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

Video Tutorial: iOS App Extensions Part 5: Today Extensions: Getting Started


Porting Your App to the iPhone 6, iPhone 6 Plus and iOS 8: Top 10 Tips

$
0
0

flip

It’s the holiday season! Check out the geeky poem I wrote about this article to celebrate:

‘Twas the night before the Keynote, and no one could know
Whether new APIs would be part of the show.

Some wished for Siri, others Touch ID.
But when iOS 8 came, no one was ready.

Extensions, Swift, Metal and more.
And who would’ve guessed — universal storyboards.

Many cheered, but others started to think about support.
All these existing apps, so many we need to port!

No need to worry about porting, as this tutorial will show
New APIs and screen sizes to make your apps glow.

Follow these 10 tips and you’ll see.
iOS 8 is awesome! Ummmmmm, mom’s spaghetti?

Poems aside, now it’s time for the article itself – 10 handy tips about porting your apps to iOS 8 and the new devices – just in time for the holidays! :]

Getting Started

You might be thinking “I checked out my existing app on an iPhone 6 Plus and it displayed just fine”. You’re correct, your app should work just like before, but that doesn’t mean there’s no work to do!

Apple has done well with allowing existing apps to run in Scale Mode, and you’ll notice the app is simply scaled up to fit the larger screen. At first glance it seems okay, but you can see that the status bar is larger too. You’re definitely going to want your app to work in fullscreen mode; just look at the difference:

Fullscreen vs Scaled

Fullscreen mode allows the app to display much more information in the same screen space. Notice that the status bar size is different as well. The text may look too small in this photo, but it looks just right on the Retina HD Screen.

Now that you’re on-board to support fullscreen mode, there’s one more question that needs to be answered: Should you drop iOS 7 support?

You’re going to have to make the final call, but here are a few points to consider:

  • iOS 7 users will still have access to the current version in the App Store.
  • The large screen devices (iPhone 6 and iPhone 6 Plus) only run iOS 8 or higher.
  • iOS 8 adoption rate, although lower than what iOS 7 was in the same timeframe, is already over 60%.
  • All the glorious new APIs. :]

newAPIs

Alright, now that you’ve made up your mind, here are the top 10 tips for porting your app to the iPhone 6, iPhone 6 Plus and iOS 8, grouped into three sections.

Note: If you do choose to target iOS 7 for your update, you can still take advantage of the new APIs. Check out Supporting Multiple iOS Versions and Devices for tips on how to support multiple iOS versions and devices in the same app.

Section 1 – Supporting the New Screen Sizes

Tip 1: Adopting Adaptive Layout and the Universal Storyboard

If your app uses storyboards and auto-layout, good for you; porting will be a breeze. If not, right now is the best time to start doing so. Size classes require auto-layout, and with Universal Storyboards, Apple has made it pretty clear that storyboards will be a centerpiece for iOS apps in the future.

Furthermore, the latest SDK WatchKit won’t even work without one.

The good news is that if you haven’t adopted them yet, raywenderlich.com has some great tutorials on storyboards and auto layout. A couple of my favorites:

To take the first step towards implementing adaptive layout, you first want to turn your existing storyboard into an universal storyboard — a storyboard that can handle your interface for any screen size.

It only takes one click to do so! Open up your storyboard and go to the info panel (command + alt + 1) and check the box for Use Size Classes:
Screen Shot 2014-12-14 at 11.25.43 PM

You’ll notice that your views all turn into squares. Don’t get too excited though, as that doesn’t mean your app now supports the Blackberry Passport. It’s just an arbitrary size that now works for any size screen.

Your constraints should all be kept the same as before, and nothing has really changed (yet!). You can confirm this by using the new Preview mode in the Assistant Editor:

Screen Shot 2014-12-14 at 11.28.27 PM

What you can do now is set up your constraints independently for different size classes. Check out our Beginning Adaptive Layout for an in-depth tutorial on adaptive layout, size classes and universal storyboards.

Note: If your app is universal, chances are you have separate storyboards for your iPhone and iPad interfaces. You have two options here:

  1. Continue to use separate storyboards. Turn only your iPhone storyboards, or all your storyboards, into universal ones and still use them separately for the iPhone and iPad.
  2. Go all out and replace them with a single universal storyboard! This allows you to only maintain a single storyboard going forward. However, this approach likely requires quite a bit of work. Currently, there are no refactoring tools in Xcode to help you with this so you’re on your own with the conversion.

Tip 2: Enabling Fullscreen Mode and the Launch Screen File

As you recall, fullscreen mode is much better than scale mode, and all that work you just did to adopt adaptive layout doesn’t offer any rewards unless your app is in fullscreen mode. Fortunately, enabling fullscreen mode is very easy, and it comes with an awesome bonus perk!

According to Apple:

At runtime, the system looks for a storyboard launch screen file, so as to let the system know your app supports iPhone 6 screen sizes, including a storyboard launch screen file in your app’s bundle

If such a file is present, the system assumes your app explicitly supports the iPhone 6 and 6 Plus and runs it in fullscreen mode.

Wait – a launch screen storyboard? You’re probably asking, “Is that what I think it is? Do I no longer need to provide 20 launch screen images?”

Yes, yes and yes!

To get started, add a new file to your app by going to New File…. In iOS > User Interface, there is a new file type called Launch Screen that add — as the name suggests — a new launch screen to your app! Beware though, Xcode creates a rather unappealing one for you:

Screen Shot 2014-12-14 at 11.35.00 PM

Rather amusingly, after all that talk about storyboards, Xcode creates a xib for you. Go ahead and clear out the existing, hideous labels in the xib and set up the launch screen as you like. If you want to keep it the same as before, simply add a UIImageView with your launch image.

Finally, go to your project’s general settings and choose your new xib file for the field Launch Screen File, like this:
Screen Shot 2014-12-14 at 11.23.39 PM

Build and run your app on the iPhone 6 Plus simulator, enjoy your new launch screen and marvel at your app running fullscreen mode.

Note: If you still support iOS 7 and/or earlier, you still need to supply 4-inch launch images. If you don’t, your app will display in 3.5″ mode.

Tip 3: Better-Than Retina Displays and @3x Images

The iPhone 6 Plus brings with it a new Retina HD display with 401 PPI. To support that amazing resolution, you now need to supply images in 3 times resolution. Just like @2x images, all you need to do is supply @3x images and iOS will load the correct image for you.

Note:The iPhone 6 also has a Retina HD display, but has the same pixel density as previous Retina iPhones and still loads the @2x assets.

With your app in fullscreen mode you must feel quite happy.
There’s still much to do though, don’t leave in a hurry!

Section 2 – User Permission Changes

iOS 8 comes with privacy changes that users will sure love.
Unfortunately, your app will break if they are incorrectly made use of.

In this section, you’ll fix these issues and keep your users happy.
Just keep reading along, and follow these tips three:

Tip 4: Fix Location Permissions

iOS 8 uses two new permissions to request a user’s location: one that only receives updates when the app is running, and another to receive updates even when your app is not running.

Before, iOS would ask for permissions for your app automatically whenever you started to monitor the location. This changed in iOS 8, and you need to explicitly request permissions before starting the location updates.

To do so, you need to explicitly call requestWhenInUseAuthorization or requestAlwaysAuthorization on the CLLocationManager if the current authorization status is undetermined.

Simply add this call before you call startUpdatingLocation on the manager. Here’s a very simple example:

  self.locationManager = [[CLLocationManager alloc] init];
  self.locationManager.delegate = self;
  if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
    [self.locationManager requestWhenInUseAuthorization];
  }
  [self.locationManager startUpdatingLocation];

One last step: In your app’s info.plist, you need to add either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription as a new key, and enter a string that’ll show to the user with the permission-request prompt. For instance, “Location is required to show you nearby items.”

Screen Shot 2014-12-14 at 11.44.19 PM

iOS Simulator Screen Shot Dec 14, 2014, 11.44.36 PM

Tip 5: Fix Notification Registration

User notification permissions have changed, mostly to support actionable notifications. All the previous APIs are deprecated and will not work in iOS 8.

There are now two layers of notification permissions. Your app must first request permission to display certain types of notifications. Once the user grants those, you need to request permission to receive these notifications remotely.

Previously, you could call -registerForRemoteNotificationTypes: right inside -application:didFinishLaunchingWithOptions: and receive the delegate callbacks to check the status. If you did the same in iOS 8, you’d notice the delegate methods are not called at all.

This is because you need to request the first layer of permission first. Here is a simple example inside the appDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // 1
  UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
  // 2
  [application registerUserNotificationSettings:settings];
 
  return YES
}
 
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
  // 3
  if (notificationSettings.types != UIUserNotificationTypeNone) {
    // 4
    [application registerForRemoteNotifications];
  }
}
 
// 5
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  // ... (no changes needed)
}
 
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
// ... (no changes needed)
}

It is one more callback longer than before. Here is a quick explanation of the steps:

  1. You first create an UIUserNotificationSettings, a settings object that defines the type of notification your app needs to display, as well as the categories that define the actions.
  2. Call -registerUserNotificationSettings: and pass in the settings object. This prompts the user for the permissions.
  3. Once the user responds, the new delegate method -application:didRegisterUserNotificationSettings: is called. The notificationSettings passed in here do not necessarily match what you passed in from step 2. They describe only the permissions the user granted. You’d check `types` to verify which permissions were granted.
  4. If the user has granted you permissions, you can now call -registerForRemoteNotifications. Note how this method no longer accepts parameters. Those settings are now captured by the settings object, and here you’re only requesting to receive notifications remotely.
  5. You can then obtain the device token through the same callbacks as before.

Tip 6: Kindly Asking for Permissions…Again

If a user denies a permission the first time the prompt appears, they won’t see the prompt again. If users deny a permission that is essential, it’s common for apps to show an error page or an alert to direct users to Settings\Privacy to enable the required permissions. It was a bit clunky and many an app received a poor review due to this.

iOS 8 simplifies all this by providing UIApplicationOpenSettingsURLString, a constant string that takes users directly to your app’s settings when passed to -openURL:. It makes asking for permissions much simpler.

Add the following line to the action of a button or alert to take advantage of this:

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];

Settings

Section 3 – Beyond Porting

Now your app functions on iOS 8, congratulations, the new APIs of iOS 8 await.
The porting of features is finally done. But with your app fully working, it’s now time to have fun :]

Tip 7: Unleash The Power of Swift

Using Swift introduces several benefits, most noticeably the reduction of file count and overall lines of code. You don’t have to rewrite any existing code with Swift but you can definitely start using it for new classes and files, even in existing projects.

To make sure that Swift and Objective-C work play nice in your project, follow these tips:

  1. When you add the first Swift file to your project Xcode will create a bridging header for you. Make sure you allow it to do so.
  2. If you want to use your Objective-C class in your Swift file, import your Objective-C header file into the bridging header using a standard #import.
  3. If you want to use a Swift class in your Objective-C, import the umbrella header ProductModuleName-Swift.h into your .m files, where ProductModuleName is the name of your module. In many cases this will be your project name, but you can double check in your project settings under Product Module Name.
  4. Make sure your Swift classes either inherit from an Objective-C class (i.e. NSObject) or are marked with @objc

To learn more about Swift, be sure to check out all our Swift tutorials and also our iOS tutorial books, most of which have been updated to Swift, or written about it.

If you want to learn more about using Swift and Objective-C together, refer to Apple’s Using Swift with Cocoa and Objective-C guide.

Tip 8: Significant API Deprecations/Updates

iOS 8 updates a few fundamental pieces of Cocoa Touch. UIAlertView, UIActionSheet and UISearchDisplayController all have new replacements that are much more pleasant to work with, and UIPresentationController now has much-needed control over the view controller presentation process.

UIAlertController replaces both UIAlertView and UIActionSheet with a clean, block-based interface. A simple usage example is:

UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Are you sure?" message:@"Once deleted this item cannot be recovered." preferredStyle:UIAlertControllerStyleAlert];
 
[alert addAction: [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self deleteItem ]}];
[alert addAction: [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:{}];
 
[self presentViewController:alert animated:YES completion:nil];

As you can see, you don’t need a delegate anymore and you show it using -presentViewController: that just feels right. UIAlertController also brings new functionality like multiple buttons and simple configuration of text fields in alerts.

Take a look at the great article on UIAlertController from NSHipster to learn more.

UIPresentationController gives you all the control you could ever want when presenting a view controller. You can fully adjust the chrome, position and animations of presentations in a clean, reusable manner.

Check out the official documentation or Chapter 6, “Introducing Presentation Controllers” in iOS 8 by Tutorials to learn all about presentation controllers.

Tip 9: Cool New Visual Effects

One of the beloved additions in iOS 7 was the blur effect. Apple finally delivered this API in iOS 8 with the brand new UIVisualEffectView and UIVibrancyEffectView classes!

vibrancy

UIVisualEffectView is an effective way to de-emphasize the background and UIVibrancyEffectView makes the foreground really pop. They are optimized for performance, but if overused they will slow down your app’s performance, so be careful.

If you want to get started with visual effects hop right in to our iOS 8 Visual Effects Tutorial.

Tip 10: A New World of Options

This tutorial is about making your app run on iOS 8, and if you’ve followed the tips up to now, consider it a job well done. The good (bad?) news is this is still just the beginning, iOS 8 has just so much to offer!

There are numerous ways to extend the functionality of your app in iOS 8, and these were not possible before. Here are just a few more ways you can spice up your app:

  • Use WatchKit and create a Watch Extension for your app.
  • Create a Today Extension for users to see information in their notification panel.
  • Use Document Providers for users to share files between apps.
  • Add Actions to your notifications.
  • Integrate Touch ID to let users authenticate with ease.

And much, much more… make sure you take a look at iOS 8 Tutorials and WatchKit by Tutorials if you want to dig deeper into what iOS 8 and WatchKit have to offer.

Where To Go From Here?

iOS 8 opens the door to more functionality than ever before.
With all these new tools it feels like a beginning, once more.

I hope that you enjoyed this, and found these tips useful.
Now go on, dear friend, and create something joyful :]

This tutorial was quite crammed, and I hope it made sense.
Here are all the links, for a quick reference:

There is much do learn, but it is all so exciting.
Please share your thoughts and comments below, but remember, no fighting!

Porting Your App to the iPhone 6, iPhone 6 Plus and iOS 8: Top 10 Tips is a post from: Ray Wenderlich

The post Porting Your App to the iPhone 6, iPhone 6 Plus and iOS 8: Top 10 Tips appeared first on Ray Wenderlich.

Video Tutorial: iOS App Extensions Part 6: Today Extensions: Layout

iOS Animation with Swift Video Tutorial Series Updated for Xcode 6.1.1

$
0
0

AnimationUpdated

This is just a quick note to let you know that Marin Todorov has updated his popular iOS Animation with Swift video tutorial series to Xcode 6.1.1.

Specifically, Marin updated all of his sample projects to Xcode 6.1.1. He found that most of the changes were minor, related to the recent refactorings Apple has done with their API calls and whether they take optionals or implicitly unwrapped optionals.

This update is important to note, because the series was originally developed before Xcode 6 was out of beta, so took a fair bit of work to update. We’re doing our best to keep things up-to-date for everyone with this and our other written and video tutorials!

Marin and I would like to extend a huge thanks to all subscribers for supporting these videos, and we’ll definitely keep them coming! :]

iOS Animation with Swift Video Tutorial Series Updated for Xcode 6.1.1 is a post from: Ray Wenderlich

The post iOS Animation with Swift Video Tutorial Series Updated for Xcode 6.1.1 appeared first on Ray Wenderlich.

Video Tutorial: iOS App Extensions Part 7: Today Extensions: Core Data

Merry Christmas 2014!

$
0
0

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

This year, we have made a song titled “Xcode Crash”, sung to the tune of “Jingle Bells.” We hope you enjoy – and we apologize in advance for our singing! :]

Lyrics and Credits

Parsing through code (Matthijs Hollemans)
Not one line astray (Mic Pringle)

Now to Swift we go (Eric Cerney)
Time to crash away! (Eric Cerney)
Dreaded popup rings (Chris Belanger)
Making faces bright (Vicki Wenderlich, Ray Wenderlich, Samantha Schulz, and B.C. Phillips)
What fun it is to rage and scream (Ellen Shapiro)
when SourceKit crashes tonight! (Tammy Coron)

Oh! Xcode crash (Joshua Greene and family)
Xcode crash (Cesare Rocchi)
Xcode crash away (Brian Moakley)
Oh what fun it is to code (Chris Belanger)
With SourceKit in my way – hey! (Matthijs Hollemans)

Xcode crash, Xcode crash (Tim Mitra)
Xcode crash away (Ellen Shapiro)
Oh what fun it is to code (Eric Cerney)
With SourceKit in my way! (Eric Cerney)

Oh what fun it is to code (Brian Moakley)
With SourceKit (Mic Pringle)
in my way! (Vicki Wenderlich, Ray Wenderlich, Samantha Schulz, and B.C. Phillips)

Special thanks to Ellen Shapiro for the guitar music and background singing, and Chris Belanger for the piano music!

But Wait, There’s More!

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

That’s It!

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

Merry Christmas 2014! is a post from: Ray Wenderlich

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

Video Tutorial: iOS App Extensions Part 8 Today Extensions: Updating Data

Video Tutorial: iOS App Extensions Part 9: Today Extensions: OpenURL


Video Tutorial: iOS 101 with Swift Part 0: Overview

Video Tutorial: iOS 101 with Swift Part 1: App Structure

How To Create a Breakout Game with Sprite Kit and Swift

$
0
0
Your little Breakout game. Destroy all the blocks!

Breakout blocks at the beginning of the game – all in a row.

Update note: This tutorial was updated to Swift and iOS 8 by Zouhair Mahieddine. Original post by Tutorial Team member Barbara Reichart.

Sprite Kit is Apple’s game development framework for iOS and OS X. Not only does it come with some great graphics capabilities, but it also includes a physics engine which looks a lot like Box2D. Best of all, you do all the work using the tools you are familiar with: Swift, Xcode and Interface Builder!

There’s a lot you can do with Sprite Kit, and a great way to start learning about how it works is to create a simple game.

In this tutorial, you are going to learn how to create a Breakout game using Sprite Kit step-by-step, complete with collision detection, ball bouncing using physics effects, dragging the paddle via touches, and win/lose screens.

If you are new to Sprite Kit, you should go through the Sprite Kit Swift Tutorial for Beginners before proceeding with this tutorial.

Getting Started

Start by creating a new project. For this start up Xcode, go to File\New\Project… and choose the iOS\Application\Game template. Set the product name to BreakoutSpriteKitTutorial, select Language > Swift (obviously…), Game Technology > SpriteKit, Devices > iPhone and then click Next. Select a location on your hard drive to save your project and then click Create.

Your game will need some graphics. You probably want to have at least a graphic for the ball, the paddle, the bricks, and a background image. Start by downloading our starter image pack. Open Images.xcassets and drag and drop the files to the panel with the AppIcon asset in Xcode. Delete the default Spaceship asset that comes with the Sprite Kit template because it wont be used in this tutorial.

Open GameScene.swift. This class creates your game scene. The template includes some extra code that you will not need. So, replace the contents of the file with the following:

import SpriteKit
 
let BallCategoryName = "ball"
let PaddleCategoryName = "paddle"
let BlockCategoryName = "block"
let BlockNodeCategoryName = "blockNode"
 
class GameScene: SKScene {
  override func didMoveToView(view: SKView) {
    super.didMoveToView(view)
  }
}

The code here is very basic. First, you define a few constants that will help you identify the game objects. Then, didMoveToView(_:) gives you a starting point to access your sprites and work on them.

Now you have a barebones project to fill in with your masterpiece!

Note: By default the template code is configured to display the number of nodes in your SKScene and its frame rate. You can easily remove this information from the screen – or add others, like some very useful physics-related debugging information – by opening GameViewController.swift and changing these two lines to set the properties to false:
skView.showsFPS = true
skView.showsNodeCount = true

However, the screen is still in portrait mode and you want Breakout to work in landscape mode instead. To fix that, select the project root in Xcode, then select the BreakoutSpriteKitTutorial target, make sure that the General tab is selected, and set the orientation to just landscape by ensuring that only the two landscape checkboxes are checked as shown below:

Set the orientation to landscape-only

Build and run! Now the gray screen only shows up in landscape, even if you rotate the device to portrait.

BreakoutGray

Introducing the Sprite Kit Visual Editor

You will now add a background to the scene. To do that, open the GameScene.sks file. This is a visual editor already linked to your Sprite Kit Scene and every element dropped in there will be accessible in your game and from GameScene.swift.

First, you will have to resize the scene so it fits your targeted screen for this tutorial: an iPhone 5 screen. You can do that in the Scene section of the SKNode inspector on the top right corner of the Xcode window. If you don’t see the SKNode inspector, you can access it via View\Utilities\Show SKNode Inspector. Set the scene’s size to 1136x640 as shown in the screenshot below.

Set the scene's size

Now to the game’s background. As shown in the screenshot below, drag a Color Sprite from the Object Library panel on the bottom right corner of the Xcode window. If you don’t see the Object Library panel, just click on View\Utilities\Show Object Library and it will show up.

Add the scene's background

Using the SKNodeInspector, change the Size and Position of the sprite so that it fills the entire scene (position = {568,320} ; size = 1136x640).

Finally, set its Texture to be bg.png.

You can now build and run (targeting an iPhone 5, 6 or even 6 Plus) to see your game display only a background.
BreakoutBackground

Note: At the time this tutorial is written, Xcode 6 has a few bugs in the Sprite Kit Visual Editor. Sometimes, you will not see your changes being reflected on your canvas. Just build and run your project and everything should be ok. If for some reason your canvas loses some of your settings… well then, there is really no better technique than doing it again while shouting and swearing at Xcode (any damage caused to your computer in these situations should not be considered the fault of yours truly…). Oh, and if these bugs are fixed by the time you are reading this… Well, it’s about time!

An-Ever-Bouncing Ball

Once you have a nice clean landscape scene with a background, it is time to add the ball! Again, you are going to do that using the visual editor. Still in GameScene.sks, drag a new Color Sprite into the scene, placing it wherever you want. Then, set its Texture to ball.png and its Name to ball.

Build and run and…! Your ball is static…

BreakoutBallStatic

That is because you need to add some physics to it.

Physics

In Sprite Kit you work in two environments: the graphical world that you see on the screen and the physics world, which determines how objects move and interact.

The first thing you need to do when using Sprite Kit physics is to change the world according to the needs of your game. The world object is the main object in Sprite Kit that manages all of the objects and the physics simulation. It also sets up the gravity that works on physics bodies added to it. The default gravity is -9.81 thus similar to that of the earth. So, as soon as you add a body it would “fall down”.

Once you have created the world object, you can add things to it that interact according to the principles of physics. For this the most usual way is to create a sprite (graphics) and set its physics body. The properties of the body and the world determine how it moves.

Bodies can be dynamic objects (balls, ninja stars, birds, …) that move and are influenced by physical forces, or they can be static objects (platforms, walls, …) that are not influenced by those forces. When creating a body you can set a ton of different properties like shape, density, friction and many more. Those properties heavily influence how the body behaves within the world.

When defining a body, you might wonder about the units of their size and density. Internally Sprite Kit uses the metric system (SI units). However within your game you usually do not need to worry about actual forces and mass, as long as you use consistent values.

Once you’ve added all of the bodies you like to your world, Sprite Kit can take over and do the simulation. To set up your first physics body, select the ball node you just added and scroll down the SKNode Inspector until you reach the Physics Definition section. Select Body Type>BoundingCircle and set the newly appeared properties to the following:

  1. Uncheck Allows Rotation
  2. Set the Friction to 0
  3. Set the Restitution to 1
  4. Set the linear Damping to 0
  5. Set the Angular Damping to 0
Adding physics to the ball

Adding physics to the ball

No surprises here. You simply create a sprite and name it for later reference. Then, you create a volume-based body for the ball. This physics body is affected by forces or impulses, and collisions with other bodies. Here you create a physics body with the form of a circle that has exactly the same size as the ball sprite.

As for the properties of this physics body:

  1. AllowsRotation does exactly what the name implies. It either allows rotation of the body or not. Here you do not want the ball to rotate.
  2. Friction is also quite clear – it simply removes all friction.
  3. Restitution refers to the bounciness of an object. You set the restitution to 1, meaning that when the ball collides with an object the collision will be perfectly elastic. In plain English, this means that the ball will bounce back with equal force to the impact.
  4. Linear Damping simulates fluid or air friction by reducing the body’s linear velocity. In the Breakout game the ball should not be slowed down when moving. So, you set the damping to 0.
  5. Angular Damping is the same as Linear Damping but for the angular velocity. Setting this is optional as you don’t allow rotation for the ball.
Note: Usually, it’s best to have the physics body be fairly similar to what the player sees. For the ball it’s very easy to have a perfect match. However, with more complex shapes you’ll have to get a bit more creative. This is where you’ll need to be careful since very complex bodies can exact a high toll on performance. Lucky for you, since iOS 8 and Xcode 6, Sprite Kit supports alpha masks body types, a good way to automatically take the shape of a sprite as the shape of its physics body, but be careful as it can cause performance issues.

Build and run. If you’re quick enough you should see the ball just fall through the scene and disappear at the bottom of the screen.
BreakoutBallFalling

Two reasons for this: first, the default gravity of a scene simulates that of the earth – 0 along the x-axis and -9.8 along the y-axis. Second, your scene’s physics world has no boundaries that would act as a cage enclosing the ball. Let’s start with that!

Caging the ball

No escape for the ball.

No escape for the ball.

Open GameScene.swift and add the following line of code to the end of didMoveToView(_:) (right after the call to super.didMoveToView(view)) to create an invisible barrier around the screen (this will effectively cage the ball on screen, ensuring that it cannot escape):

// 1. Create a physics body that borders the screen
let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
// 2. Set the friction of that physicsBody to 0
borderBody.friction = 0
// 3. Set physicsBody of scene to borderBody
self.physicsBody = borderBody
  1. You create an edge-based body. In contrast to the volume-based body you added to the ball, an edge-based body does not have mass or volume, and is unaffected by forces or impulses.
  2. Set the friction to 0 so that the ball will not be slowed down when colliding with the border barrier. Instead, you want to have a perfect reflection, where the ball leaves along the same angle that it hit the barrier.
  3. You can set a physics body for every node. Here, you attach it to the scene. Note: The coordinates of the SKPhysicsBody are relative to the position of the node.
Perfect reflection

Perfect reflection

Running your project, you should now see your ball fall as before but it now bounces on the bottom-edge of the cage you just added. And because you removed all kinds of frictions (from the contact with the cage and the environment) and set the restitution to be perfectly elastic, the ball will bounce like that indefinitely, not being slowed down by anything.

BreakoutBouncingball

To be finished with the ball, let’s now remove the gravity and apply just a single impulse to the ball so that it will bounce around the screen forever.

Forever bouncing

It’s time to get the ball rolling (bouncing, actually). Add the following code right after the previous lines in GameScene.swift:

physicsWorld.gravity = CGVectorMake(0, 0)
 
let ball = childNodeWithName(BallCategoryName) as SKSpriteNode
ball.physicsBody!.applyImpulse(CGVectorMake(10, -10))

This new code first removes all gravity from the scene, then gets the ball from the scene’s child nodes using the name you set in the Visual Editor and applies an impulse. An impulse applies an immediate force to a physics body to get it moving in a particular direction (in this case, diagonally down to the right). Once the ball is set in motion, it will simply bounce around the screen because of the barrier you just added!

Now it’s time to try it out! When you compile and run the project, you should see a ball continuously bouncing around the screen – cool!

BreakoutBallBouncing

Warning: If you are following this tutorial using the first versions of Xcode 6, you might see your ball behave weirdly. That is because the Sprite Kit Editor is still new (and has some bugs). Some properties set in it might not be taken in account while running the app (in my case, it was ignoring the linearDamping value…). To prevent that, you can rely on good old code and add this right after the previous lines in GameScene.swift:
ball.physicsBody!.allowsRotation = false
ball.physicsBody!.friction = 0
ball.physicsBody!.restitution = 1
ball.physicsBody!.linearDamping = 0
ball.physicsBody!.angularDamping = 0

Try it out again, it should be cool now!

Adding the Paddle

It wouldn’t be a Breakout game without a paddle, now would it?

Go to GameScene.sks and build the paddle (and its companion physics body) via the Visual Editor just like you did for the ball by placing a Color Sprite at the bottom middle of the scene and setting its properties to:

  1. Name = paddle
  2. Texture = paddle.png
  3. Body Type > Alpha mask
  4. Uncheck Dynamic
  5. Friction: 0
  6. Restitution: 1

Most of this is similar to what you used when creating the ball. However, this time you use an Alpha mask to form the physics body as it will exactly match the rounded corners of the paddle.

Here you need to make sure that the paddle is static. This ensures that the paddle does not react to forces and impulses. You will soon see why this is important.

If you build and run, you’ll see your paddle in the scene, and the ball will bounce off it (if you can wait long enough for it to get there :]):

The most boring game ever :/

The most boring game ever :/

However, this isn’t much fun, because you can’t move the paddle yet!

Moving the Paddle

Time to get moving! Moving the paddle is going to require detecting touches. You can detect touches in GameScene by implementing the following touch handling methods:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
override func touchesMoved(touches: NSSet, withEvent event: UIEvent)
override func touchesEnded(touches: NSSet, withEvent event: UIEvent)

But, before that, you need to add a property. Go to GameScene.swift and add the following property to the class:

var isFingerOnPaddle = false

This defines a property that stores whether the player tapped the paddle or not. You will need it to implement the dragging of the paddle.

Now, go ahead and add the code to implement touchesBegan(_:withEvent:) to GameScene.swift as follows:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
  var touch = touches.anyObject() as UITouch!
  var touchLocation = touch.locationInNode(self)
 
  if let body = physicsWorld.bodyAtPoint(touchLocation) {
    if body.node!.name == PaddleCategoryName {
      println("Began touch on paddle")
      isFingerOnPaddle = true
    }
  }
}

The above code gets the touch and uses it to find the location on the scene where the touch occurred. Next, it uses bodyAtPoint(_:) to find the physics body associated with the node (if any) at that location.

Finally, it checks whether there was a node at the tap location and if yes, whether that node is the paddle. This is where the object names you set up earlier come into play – you can check for a specific object by checking its name. If the object at the tap location is a paddle, then a log message is sent to the console and isFingerOnPaddle is set to true.

Note: To do these checks, you use a new technique called Optional Binding. What this does is check if an object exists, and if so, unwrap and bind it to a new variable that can be used within the if statement.

Now you can build and run the project again. When you tap the paddle, you should see a log message in the console.
BreakoutDebugger1

Now, go ahead and add the code for touchesMoved(_:withEvent:):

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
  // 1. Check whether user touched the paddle
  if isFingerOnPaddle {
    // 2. Get touch location
    var touch = touches.anyObject() as UITouch!
    var touchLocation = touch.locationInNode(self)
    var previousLocation = touch.previousLocationInNode(self)
 
    // 3. Get node for paddle
    var paddle = childNodeWithName(PaddleCategoryName) as SKSpriteNode!
 
    // 4. Calculate new position along x for paddle
    var paddleX = paddle.position.x + (touchLocation.x - previousLocation.x)
 
    // 5. Limit x so that paddle won't leave screen to left or right
    paddleX = max(paddleX, paddle.size.width/2)
    paddleX = min(paddleX, size.width - paddle.size.width/2)
 
    // 6. Update paddle position
    paddle.position = CGPointMake(paddleX, paddle.position.y)
  }
}

This is where most of the paddle movement logic comes in.

  1. Check whether the player is touching the paddle.
  2. If yes, then you need to update the position of the paddle depending on how the player moves their finger. To do this, you get the touch location and previous touch location.
  3. Get the SKSpriteNode for the paddle.
  4. Take the current position and add the difference between the new and the previous touch locations.
  5. Before repositioning the paddle, limit the position so that the paddle will not go off the screen to the left or right.
  6. Set the position of the paddle to the position you just calculated.

The only thing left in touch handling is to do some cleanup in touchesEnded(_:withEvent:) as follows:

override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
  isFingerOnPaddle = false
}

Here, you set the isFingerOnPaddle property to false. This ensures that when the player takes their finger off the screen and then taps it again, the paddle does not jump around to the previous touch location.

Perfect! When you build and run the project now, the ball will bounce around the screen and you can influence its movement using the paddle.
BreakoutBallBouncingPaddle

Note: You might have noticed that the code directly manipulates the position of the paddle. You can do this because you made the paddle static. You should never change the position of a dynamic body directly, as it can break the physics simulation and lead to really weird behavior.

Don’t believe me? Make the paddle dynamic and set a low gravity, e.g. this

physicsWorld.gravity = CGVectorMake(-0.1, -0.1)

Looks quite messed up, doesn’t it?

Sprite Kit Makes First Contact!

No! Not that type of first contact.(image by DeviantArt user hugoo13, Creative Commons Licensed)

No! Not that type of first contact! (image by DeviantArt user hugoo13, Creative Commons Licensed)

So far, you have a ball that bounces around the screen and a paddle you can move around via touch. While this is really fun to toy around with, to make it a game you need a way for your player to win and lose the game. Losing should happen when the ball touches the bottom of the screen instead of hitting the paddle. But how do you detect this scenario using Sprite Kit?

Sprite Kit can detect the contact between two physics bodies. However, for this to work properly, you need to follow a few steps to set things up a certain way. Here’s a short overview. Each of the steps will be explained in more detail later. So, here you go:

  • Set up physics body bit masks: In your game you might have several different types of physics bodies – for example, you can have the player, enemies, bullets, bonus items etc. To uniquely identify these different types of physics bodies, each physics body can be configured using several bit masks. These include:
    • categoryBitMask: This bit mask identifies the category a body belongs to. You use categories to define a body’s interaction with other bodies. The categoryBitMask is a 32-bit integer, where each bit represents one category. So you can have up to 32 custom categories in your game. This should be enough for most games to set up a separate category for each object type. For more complex games it can be useful to remember that each body can be in several categories. So through smart design of the categories you could even overcome the limitation of 32 categories.
    • contactTestBitMask: Setting a bit in this bitmask causes Sprite Kit to notify the contact delegate when the body touches another body assigned to that particular category. By default, all bits are cleared – you are not notified about any contacts between objects. For best performance you should only set bits in the contacts mask for interactions you are really interested in.
    • collisionBitMask: Here, you can define which bodies can collide with this physics body. You can use this, for example, to avoid collision calculations for a very heavy body when it collides with a much lighter body as this would only make negligible changes to the heavy body’s velocity. But you can also use it to allow two bodies to pass right through each other.
  • Set and implement the contact delegate: The contact delegate is a property of SKPhysicsWorld. It will be notified when two bodies with the proper contactTestBitMasks begin and end colliding.
Note: Bit masks?!? In case you’ve never worked with bit masks, don’t panic! At first glance they might look complicated, but they are really useful.
So what is a bitmask? A bitmask is a multi-digit binary number. Something like this: 1011 1000. Not so complicated at all.

But why are they useful? Well, they allow you to get state information out of a binary number and give you the ability to change a specific bit in a binary number to set a specific state. You can do this with the binary operators AND and OR, like so:

Bitmask power :)

Bitmask power :]

This allows you to store a lot of information in a really compact way using just one variable and still be able to access and manipulate the stored information.

So how do you do all this in code?

First, create constants for the different categories. Do this by adding the following lines below the other constants for the category names in GameScene.swift:

let BallCategory   : UInt32 = 0x1 << 0 // 00000000000000000000000000000001
let BottomCategory : UInt32 = 0x1 << 1 // 00000000000000000000000000000010
let BlockCategory  : UInt32 = 0x1 << 2 // 00000000000000000000000000000100
let PaddleCategory : UInt32 = 0x1 << 3 // 00000000000000000000000000001000

The above defines four categories. You do this by setting the last bit to 1 and all other bits to zero. Then using the << operator you shift this bit to the left. As a result, each of the category constants has only one bit set to 1 and the position of the 1 in the binary number is unique across the four categories.
For now you only need the category for the bottom of the screen and the ball, but you should set up the others anyway as you will probably need them later on as you expand the gameplay.

Once you have the constants in place, create a physics body that stretches across the bottom of the screen. Try to do this by yourself since this uses principles you've already learned when creating the barriers around the screen edges. (Name the node containing the physics body bottom since you’ll be configuring that node in later steps.)

Solution Inside: Create an edge-based body that covers the bottom of the screen SelectShow>

With the preparations out of the way, let’s get to the meat of establishing contact. First, set up the categoryBitMasks for the bottom, ball, and paddle by adding the following code to didMoveToView(_:):

let paddle = childNodeWithName(PaddleCategoryName) as SKSpriteNode!
 
bottom.physicsBody!.categoryBitMask = BottomCategory
ball.physicsBody!.categoryBitMask = BallCategory
paddle.physicsBody!.categoryBitMask = PaddleCategory

This is really straightforward code. It simply assigns the constants you created earlier to the corresponding physics body’s categoryBitMask.

Now, set up the contactTestBitMask by adding this (again to didMoveToView(_:)):

ball.physicsBody!.contactTestBitMask = BottomCategory

For now you only want to be notified when the ball makes contact with the bottom of the screen. Therefore, you set the contactTestBitMask to BottomCategory.

Next, you need to create an SKPhysicsContactDelegate. As this is a rather simple game, you will just make GameScene the delegate for all contacts.

Change this line:

class GameScene: SKScene {

To this line:

class GameScene: SKScene, SKPhysicsContactDelegate {

This makes it official: GameScene is now an SKPhysicsContactDelegate (as it conforms to the SKPhysicsContactDelegate protocol) and will receive collision notifications for all configured physics bodies. Hurrah!

Now you need to set GameScene as the delegate in the physicsWorld. So, add this to didMoveToView(_:) right below physicsWorld.gravity = CGVectorMake(0, 0):

physicsWorld.contactDelegate = self
Setting up the SKPhysicsContactDelegate

Setting up the SKPhysicsContactDelegate

Finally, you need to implement didBeginContact(_:) to handle the collisions. Add the following method to GameScene.swift:

func didBeginContact(contact: SKPhysicsContact) {
  // 1. Create local variables for two physics bodies
  var firstBody: SKPhysicsBody
  var secondBody: SKPhysicsBody
 
  // 2. Assign the two physics bodies so that the one with the lower category is always stored in firstBody
  if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
    firstBody = contact.bodyA
    secondBody = contact.bodyB
  } else {
    firstBody = contact.bodyB
    secondBody = contact.bodyA
  }
 
  // 3. react to the contact between ball and bottom
  if firstBody.categoryBitMask == BallCategory && secondBody.categoryBitMask == BottomCategory {
    //TODO: Replace the log statement with display of Game Over Scene
    println("Hit bottom. First contact has been made.")
  }
}

Let's walk through the method step by step:

  1. Create two local variables to hold the two physics bodies involved in the collision.
  2. Check the two bodies that collided to see which has the lower categoryBitmask. You then store them into the local variables, so that the body with the lower category is always stored in firstBody. This will save you quite some effort when reacting to contacts between specific categories.
  3. Profit from the sorting that you did just before. You only need to check whether firstBody is in the BallCategory and whether secondBody is in the BottomCategory to figure out that the ball has touched the bottom of the screen, as you already know that secondBody could not possibly be in the BallCategory if firstBody is in the BottomCategory (because BottomCategory has a higher bit mask than BallCategory). For now react with a simple log message.

It’s time to try out your code again. Build and run your game and if you’ve done everything correctly, you should see the log message in the console every time the ball misses the paddle and hits the bottom of the screen. Like this:

First contact has been made :)

First contact has been made :)

Adding a Game Over Scene

Unfortunately, your players cannot see log messages when they lose the game. Instead, when they lose, you want to show them some sort of visual indication on screen. You must create a little game over scene for that purpose.

Visually building the Scene

Go to File\New\File..., choose the iOS\Resource\SpriteKit Scene template and click Next. Name the file GameOverScene and click Create.

Now, just try to repeat by yourself the steps to put the scene to the right size and add the background to it. For the record, here are some things you will need:

  • Scene size: 1136x640
  • Background texture: bg.png
Solution Inside: Mimic the GameScene via the Sprite Kit Visual Editor SelectShow>

You now have the basics of your GameOverScene's layout. Let's add a label to tell the player if he won or lost the game. In GameOverScene.sks, drag a Label from the Object Library panel to the scene and give it the following attributes using the SKNode Inspector:

  1. Name: gameOverLabel
  2. Position: {568,320}
  3. Horizontal Alignment: Center (Default)
  4. Vertical Alignment: Center
  5. Font: Just choose a font you like and that looks big enough. To help you, you can also set the label's Text to something, like Game Over (even if you will make that dynamic in code in just...right now!)
  6. Color: White (Default)

Adding some code

Go to File\New\File..., choose the iOS\Source\Cocoa Touch Class class template and click Next. Name the class GameOverScene, make it a subclass of SKScene, select Language>Swift, click Next, and then Create.
Replace the code in GameOverScene.swift with the following:

import SpriteKit
 
let GameOverLabelCategoryName = "gameOverLabel"
 
class GameOverScene: SKScene {
  var gameWon : Bool = false {
    // 1.
    didSet {
      let gameOverLabel = childNodeWithName(GameOverLabelCategoryName) as SKLabelNode!
      gameOverLabel.text = gameWon ? "Game Won" : "Game Over"
    }
  }
 
  override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    if let view = view {
      // 2.
      let gameScene = GameScene.unarchiveFromFile("GameScene") as GameScene!
      view.presentScene(gameScene)
    }
  }
}

This code is pretty standard and similar to what you've already seen. Some of the parts marked with comments might be new to you:

  1. The didSet observer attached to the gameWon property is a Swift particularity called Property Observer. With that you can observe changes in the value of a property and react accordingly. There are two property observers: willSet is called just before a property value change occurs, whereas didSet occurs just after.
  2. When the user taps anywhere in the GameOver scene, this code just presents the Game scene again. Note how it instantiates a new GameScene object by unarchiving the Sprite Kit Scene you built with the Visual Editor, referencing it by its name without the .sks extension.
Note: Property Observers have a parameter that allows you to check the new value of the property (in willSet) or its old value (in didSet) allowing value changes comparison right when it occurs. These parameters have default names if you do not provide your own, respectively newValue and oldValue. If you want to know more about this, check the Swift Programming Language documentation here: The Swift Programming Language: Declarations

The new scene is done! Time to add it to the game. Open GameScene.swift and replace the println call in didBeginContact(_:) with the following:

if let mainView = view {
  let gameOverScene = GameOverScene.unarchiveFromFile("GameOverScene") as GameOverScene!
  gameOverScene.gameWon = false
  mainView.presentScene(gameOverScene)
}

Now, build and run the game. If you lose (on purpose or not), you will observe a weird behavior: An exception is raised in GameViewController.swift in an SKNode extension class called unarchiveFromFile(_:) for the following line:

let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene

Looking closely at the line, you should easily understand what's going wrong: Xcode's SpriteKit Game template provides a built-in extension to SKNode that enables the use of the Sprite Kit Visual Editor and .sks files. However, hardcoded in this method is the name of the template-provided subclass of SKScene that you have been using as your game scene. This is probably an Xcode 6.0.2 (and earlier) error that will be corrected in future releases. In the meantime, let's make this method more generic by replacing the faulty line by the following:

let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene

Build and run the game again. Everything should work fine.
BreakoutGameOver

Adding Some Blocks – and They Are Gone...

Go to GameScene.swift and add the following code to didMoveToView(_:) to introduce the blocks to the scene:

// 1. Store some useful constants
let numberOfBlocks = 5
 
let blockWidth = SKSpriteNode(imageNamed: "block.png").size.width
let totalBlocksWidth = blockWidth * CGFloat(numberOfBlocks)
 
let padding: CGFloat = 10.0
let totalPadding = padding * CGFloat(numberOfBlocks - 1)
 
// 2. Calculate the xOffset
let xOffset = (CGRectGetWidth(frame) - totalBlocksWidth - totalPadding) / 2
 
// 3. Create the blocks and add them to the scene
for i in 0..<numberOfBlocks {
  let block = SKSpriteNode(imageNamed: "block.png")
  block.position = CGPointMake(xOffset + CGFloat(CGFloat(i) + 0.5)*blockWidth + CGFloat(i-1)*padding, CGRectGetHeight(frame) * 0.8)
  block.physicsBody = SKPhysicsBody(rectangleOfSize: block.frame.size)
  block.physicsBody!.allowsRotation = false
  block.physicsBody!.friction = 0.0
  block.physicsBody!.affectedByGravity = false
  block.name = BlockCategoryName
  block.physicsBody!.categoryBitMask = BlockCategory
  addChild(block)
}

This code creates five blocks with some padding so that they are centered on the screen.

  1. Some useful constants like the number of blocks you want and their width.
  2. Here you calculate the x offset. This is the distance between the left border of the screen and the first block. You calculate it by subtracting the width of all the blocks and their padding from the screen width and then dividing it by two.
  3. Create the blocks, configure each with the proper physics properties, and position each one using blockWidth, padding, and xOffset.

The blocks are now in place. Build and run your game and check it out!
BreakoutMissingBlocks

Wait! What? Running the game now you might not see the blocks, but if you try to direct the ball where they are supposed to be, you will see that they are there, or at least their physics body are there. Also, if you lose the game you might or might not see the text appear in the Game Over Scene. This is another "issue" caused by the template-provided code in Xcode 6.0.1 (and earlier).

To fix this, go to GameViewController.swift, locate the following line:

skView.ignoresSiblingOrder = true

And replace the value of the ignoresSiblingOrder property to false. When true, this property, for performance reasons, does not take in account the order in which you added your sprites to the scene (either via the Visual Editor or by calling addChild(_:)) when displaying them. As you are using a Sprite to display the game's background, sometimes this property can put some elements behind the background resulting in the bad behavior we were having.

The blocks are now REALLY in place. Build and run your game and check it out!

Breakout blocks at the beginning of the game. Nice and orderly.

Breakout blocks at the beginning of the game. Nice and orderly.

Not quite as expected...

Not quite as expected...

Hmm... not quite what you wanted, right? The blocks move around instead of being destroyed when touched by the ball.

In order to listen to collisions between the ball and blocks, you must update the contactTestBitMask of the ball. Go to GameScene.swift and edit the already existing line of code in didMoveToView(_:) to add an extra category to it:

ball.physicsBody!.contactTestBitMask = BottomCategory | BlockCategory

The above executes a bitwise OR operation on BottomCategory and BlockCategory. The result is that the bits for those two particular categories are set to one while all other bits are still zero. Now, collisions between ball and floor as well as ball and blocks will be sent to to the delegate.

The only thing left to do is to handle the delegate notifications accordingly. Add the following to the end of didBeginContact(_:):

if firstBody.categoryBitMask == BallCategory && secondBody.categoryBitMask == BlockCategory {
  secondBody.node!.removeFromParent()
  //TODO: check if the game has been won
}

The above lines check whether the collision is between the ball and a block. If this is the case, you remove the block involved in the collision.

You might have also noticed the ball losing momentum after hitting a block. What you really want is the blocks the be static so that they don't take any energy. To do this add the following line to the for loop where you create each block in didMoveToView(_:):

block.physicsBody!.dynamic = false

Build and run. Blocks should now disappear when the ball hits them.

Hopefully, you have a good understanding of bit masks in general and the contactTestBitMask in particular. As you read earlier, bodies also have a second bit mask – the collisionBitMask. It determines whether two bodies interact with each other. This allows you to control whether the ball bounces off the bricks or flies right through them, for instance.

If you want to practice working with bit masks, set the collisionBitMask of the ball so that it goes straight through blocks while destroying them.

Solution Inside: Using `collisionBitMask` SelectShow>

Have you overcome the challenge and tasted the thrill of victory? Time to give your player the same satisfaction of tasting victory :]

Winning the Game

To let the player actually win the game, add this method to GameScene.swift.

func isGameWon() -> Bool {
  var numberOfBricks = 0
  self.enumerateChildNodesWithName(BlockCategoryName) {
    node, stop in
    numberOfBricks = numberOfBricks + 1
  }
  return numberOfBricks == 0
}

The new method checks to see how many bricks are left in the scene by going through all the scene’s children. For each child, it checks whether the child name is equal to BlockCategoryName. If there are no bricks left, the player has won the game and the method returns true.

Now, go back to didBeginContact(_:) and replace the //TODO line in the final if condition with the following:

if isGameWon() {
  if let mainView = view {
    let gameOverScene = GameOverScene.unarchiveFromFile("GameOverScene") as GameOverScene!
    gameOverScene.gameWon = true
    mainView.presentScene(gameOverScene)
  }
}

The code checks whether the player has won by calling the new method you just implemented. If they have won, you show the GameOverScene with a message indicating victory.

Build and run. You now have a working game of Breakout! Congratulations!
BreakoutGameWon

Finishing Touches

There are a few finishing touches you can add to add a final bit of polish to the game.

Smoothing the ball's velocity

As you play the game, you may have noticed that sometimes the ball can get super-fast or super-slow, depending on how you hit it with the paddle.

You can prevent these speed variations by overriding the default update(_:) method for GameScene.swift like so:

override func update(currentTime: NSTimeInterval) {
  let ball = self.childNodeWithName(BallCategoryName) as SKSpriteNode!
 
  let maxSpeed: CGFloat = 1000.0
  let speed = sqrt(ball.physicsBody!.velocity.dx * ball.physicsBody!.velocity.dx + ball.physicsBody!.velocity.dy * ball.physicsBody!.velocity.dy)
 
  if speed > maxSpeed {
    ball.physicsBody!.linearDamping = 0.4
  }
  else {
    ball.physicsBody!.linearDamping = 0.0
  }
}

update(_:) is called before each frame is rendered. In the case of GameScene.swift, there is no update(_:) method since the default implementation has been used until now. Here, you override it with your own implementation.

You get the ball and check its velocity, essentially the movement speed. If it’s too high, you increase the linear damping so that the ball will eventually slow down.

If you build and run after this change, you should see the ball go back to a normal speed level when the speed increases too much.

Adding a Start Scene

How many times since you implemented the Game Over scene have you lost the game at launch because your ball was just moving too fast for you to catch it with the paddle?

If it never happened you are either really fast or smart enough to have placed your ball at the right place and applied the right impulse to it so that it hits the paddle the first time without you having to do anything!

That's because the game misses something every good game should have: a Start Scene!

You are now going to build one in as few steps as possible by reusing your Game Over scene!

First, you will have to make your app load a GameOverScene as its first scene. Go to GameViewController.swift and replace the following line:

if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {

With this one:

if let scene = GameOverScene.unarchiveFromFile("GameOverScene") as? GameOverScene {

Build and run. You now have a Start Scene! Unfortunately, it welcomes the player with a big "Game Over".
BreakoutGameOver

Open GameOverScene.sks and change the Text attribute of the label to say something like "Tap To Play".

Build and run! It works!
BreakoutTapToPlay

Note: Why was this so easy? Remember, you used a property observer on the gameWon property to put the right text in the gameOverLabel. As you are not setting the gameWon property when instantiating the first GameOverScene, the property observer is never called and the scene displays the text from the .sks file.

Where To Go From Here?

You can download the final project for the Sprite Kit Breakout Game that you’ve made in this tutorial.

Obviously, this is a quite simple implementation of Breakout. But now that you have this working, there’s a lot more you can do. You could extend this code to give the blocks hit points, have different types of blocks, and make the ball have to hit some of them (or all of them) a number of times before they are destroyed. You could add blocks which drop bonuses or power-ups, let the paddle shoot lasers toward the blocks, whatever you dream up!

Let me know if you have any tips or suggestions for better ways to do things, and hope this comes in handy!

How To Create a Breakout Game with Sprite Kit and Swift is a post from: Ray Wenderlich

The post How To Create a Breakout Game with Sprite Kit and Swift appeared first on Ray Wenderlich.

Video Tutorial: iOS 101 with Swift Part 2: Common UIKit Controls

Video Tutorial: iOS 101 with Swift Part 3: Beginning Auto Layout

User Accounts on iOS with Ruby on Rails and Swift

$
0
0
Add Secure User Accounts

Add Secure User Accounts

Authentication is often the foundation in most iOS applications. Whether you’re next app is the next Instagram or Facebook, the user needs to be able to open the door to the wonderful universe you create with Sign up and Sign In functions.

In a mobile environment, you’d typically implement this essential function by exposing your user services’ APIs to the app and allowing it to make changes on your server. While this sounds relatively straight forward, there is an elephant in the room — you need to do all you can to protect your user’s security and privacy.

In this tutorial you’ll:

  • Learn how to deploy your own Ruby on Rails application server on Heroku
  • Build a Swift application that interacts with the backend server to authenticate your user
  • Learn the concepts of secure API design and storage
  • Capture the moment with a selfie

What’s all this about a selfie? Well, you’ll need something practical to work with, and all the cool kids take selfies, so you’ll build an app that lets the user sign-in to your service and upload their selfie. From ducklips to crazy eyes, the user will be able to safely and securely upload and manage a gallery of selfies, all without concern over whether their image will get out to the masses and become the next meme victim on Social.

Here’s a video demoing the app that you’ll build:


Getting Started

First order of business is to set up your Heroku account and deploy the Rails backend for the app to use.

Setting Up Your Rails Application

Start by cloning the railsauth git repository. This is a simple Ruby on Rails app that already includes all the functionality your app needs. You’ll deploy this directly on Heroku, so there’s no need for you to install Ruby on Rails locally.

Open the terminal, which you can find in Applications\Utilities\Terminal, and type:

git clone https://github.com/subhransu/railsauth

If you don’t already have git installed on your Mac then you can follow this guide.

Heroku Account Creation
Next, create a Heroku account so you can deploy Rails.

If you have an existing Heroku account and have already installed the Heroku Toolbelt for Mac then you can skip directly to the next section.

Go to heroku.com and click Sign up for free

Heroku Sign up

Enter your email address and click Sign Up

Heroku Signup

Check your inbox for an email from Heroku and click on the verification link in-order to verify your email.

Heroku Confirmation

Enter a password of your choice and click Save to complete the registration process.

Heroku Signup

Next, download the Heroku Toolbelt for Mac OS X

Heroku Toolbelt

Locate the heroku-toolbelt.pkg file you just downloaded and double-click it to launch the installer.

Toolbelt Installation

When installation has finished you can quit the installer.

Deploying Rails Application on Heroku

Open the Terminal and type:

heroku login

Enter your Heroku email and password and press Enter. If you’re prompted to create an SSH key, then type Y and press Enter

Your Heroku account does not have a public ssh key uploaded.
Could not find an existing public key at ~/.ssh/id_rsa.pub
Would you like to generate one? [Yn] Y

You’ll see an “Authentication Successful” message on the Terminal. That’s your cue that the basic setup is done and you’re ready to create your first Heroku application.

Type the following into the Terminal:

heroku create

Take a note of both your Heroku application and git repository urls, which will look like this:

Creating XXXXX-XXX-1234... done, stack is cedar
http://XXXXX-XXX-1234.herokuapp.com/ | git@heroku.com:XXXXX-XXX-1234.git

Now it’s back to the Rails application you cloned earlier — you’ve not forgotten about it have you? :]

In the Terminal, change the directory to the railsauth application directory:

cd ~/location/where/you/cloned/railsauth/directory

Next, add your Heroku repository as a remote branch by using the git repo url you noted when you created the application. Enter the following, but replace the placeholder text with your url:

git remote add heroku git@heroku.com:XXXXX-XXX-1234.git

To deploy the railsauth application on Heroku, type:

git push heroku master

If it prompts you to approve the connection to Heroku, simply type yes.

The authenticity of host 'heroku.com (50.19.85.132)' can't be established.
RSA key fingerprint is 8b:48:5e:67:0e:c9:16:47:32:f2:87:0c:1f:c8:60:ad.
Are you sure you want to continue connecting (yes/no)? yes

Congratulations! You’ve successfully deployed your Rails app on Heroku. You can verify this by typing:

heroku open

This will open the application in your browser and you’ll see the friendly message, “Glad to find you here!

Heroku Deployment

Now enter the following into the Terminal:

heroku run rake db:migrate

This runs a Rails migration which creates the necessary database tables on Heroku.

That jig you do

There are two more steps you need to complete before you can start working on the Swift application.

Configuring Amazon S3 (Simple Storage Service)

You’ll store selfies on Amazon S3 (Simple Storage Service). This is a popular service for developers who need to cheaply store and retrieve files. Web developers often use S3 to store their media assets.

Go to the Amazon Web Service (AWS) Portal and click Create a Free Account

AWS Signup

Follow the on-screen instructions to create a free account, and select I am a new user.

Note: You may need to provide your credit card details. Amazon won’t charge you for 12 months, so long as you don’t exceed their Free Tier usage.

The Free Tier is more than sufficient for this tutorial and other basic projects. You can cancel the subscription at any time if you no longer wish to use S3 or any of the other services provided by Amazon.

Next, go to Amazon Web Service (AWS) Portal and click on Sign In.

Click S3 to go to S3 management console:

AWS S3

Click on Create Bucket to create an S3 bucket.

S3 Bucket Creation

S3 buckets are collections of objects, and you can store any number of objects in your bucket, kind of like a digital bag of holding. It acts much like a folder on your Mac, but it’s more fun to think of it with a mystical twist.

Enter the name of the bucket as yourname-railsauth-assets and choose your region from the drop down. Make sure you take a note of this name. Next, click Create to make an S3 bucket.

Note: Prefix the bucket with your name or username. Buckets on S3 have to be unique across all of S3. You can read more about bucket naming here.

S3 Bucket Creation

You’ll need to use your AWS security credentials in the Rails application to store selfies on S3.

In the Amazon Web Services Portal click on your name in the top-right corner and choose Security Credentials from the drop down menu.

AWS Security Credentials

Expand the Access Keys (Access Key ID and Secret Access Key) section by clicking the +.

AWS Security Credentials

Click Create New Access Key.

AWS Security Credentials

Click Show Access Key to obtain your Access Key ID and your Secret Key.

Note: Download the key file so you have a backup of the keys. You won’t be able to re-obtain your secret key if you lose it. Hence the need to download and stash it away somewhere safe.

Setting up Heroku Environment Variables

You should always use environment variables to keep your keys secure. Never hard code such things in your application code.

Open the Terminal and set the following variables one by one. Remember to replace the dummy keys with your actual AWS credentials and S3 bucket name:

heroku config:set AWS_ACCESS_KEY_ID=<Your_AWS_Access_Key_Id>
heroku config:set AWS_SECRET_ACCESS_KEY=<Your_AWS_Secret_Key>
heroku config:set S3_BUCKET_NAME="yourname-railsauth-assets"

With that complete, you now need a secret API username and password to protect your API from unwanted access.

Use this random password generator to create a 64-bit password.

Then, create an API username and password by entering the following in the Terminal:

heroku config:set API_AUTH_NAME=<USERNAME> API_AUTH_PASSWORD=<PASSWORD>

Here’s an example of what this should look like:

heroku config:set API_AUTH_NAME=MYAPIADMINNAME API_AUTH_PASSWORD=20hWfR1QM75fFJ2mjQNHkslpEF9bXN0SiBzEqDB47QIxBmw9sTR9q0B7kiS16m7e

About the APIs

Your server is now set up and ready for use, and you have eight API endpoints at the disposal of your Swift app:

  1. Sign Up
  2. Sign In
  3. Get Token
  4. Upload Photo
  5. Get Photos
  6. Delete Photo
  7. Reset Password
  8. Clear Token

The first three endpoints are locked down using HTTP Basic Authentication. The remaining endpoints expect a username and secret token. Without these details, nobody — including you — can access the APIs directly. Also, the temporary token has a time limit.

You encrypt the user’s password with the AES (Advanced Encryption Standard) encryption algorithm.

Refer to the documentation of the API to find out more, including the request and response format.

Getting Started with the Swift Application

You’re all done with the backend setup, so now you get to play around with Swift :]

Start by downloading the starter project for this tutorial.

Open Main.storyboard and you’ll see the user interface; this have been provided to allow you to focus on Swift:

Storyboard

SelfieCollectionViewController is the main view controller and contains a collection view where you’ll display the selfies.

Another key point to know is that each collection view cell contains an image view to display the selfie, and a label to display the caption.

Take a look at viewDidAppear(_:):

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(true)
 
  let defaults = NSUserDefaults.standardUserDefaults()
 
  if defaults.objectForKey("userLoggedIn") == nil {
    let loginController: ViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ViewController") as ViewController
    self.navigationController?.presentViewController(loginController, animated: true, completion: nil)
  }
}

Here you check if the user is already logged in, and if they aren’t you prompt the user to Sign in. Specifically, it checks for a userLoggedIn flag in NSUserDefaults.

Note: Although data stored in NSUserDefaults persists across restarts, you should never use it to store any sensitive information like a users email or password. It resides in the apps Library folder which is accessible to anyone with physical access to the device. However, it’s perfectly fine to use it for storing non-sensitive data like preference settings or temporary flags, like you’re doing above.

Build and run. You should see the Sign in view.

sign_in

Now you’re ready to take the world by storm and build an app that lets your users capture the essence of their very souls with a single tap, by snapping a selfie :]

You’ll need to complete five primary tasks:

  1. Let a user create a new account
  2. Allow a user to sign in to your application
  3. Display selfies that the user has already uploaded
  4. Upload new selfies
  5. Delete old, dated and undesirable selfies. After-all, everyone likes a second chance to make the perfect funny face!

Before you can proceed, you need to spend a little more time working with the server and API details — specifically, you need to update them in your application.

Open HTTPHelper.swift from the Selfie group and update API_AUTH_NAME, API_AUTH_PASSWORD, and BASE_URL with your heroku server details.

static let API_AUTH_NAME = "<YOUR_HEROKU_API_ADMIN_NAME>"
static let API_AUTH_PASSWORD = "<YOUR_HEROKU_API_PASSWORD>"
static let BASE_URL = "https://XXXXX-XXX-1234.herokuapp.com/api"

Make sure that BASE_URL still has /api at the end.

With the server details updated, you’re now ready to implement the authentication flow!

Sign up and Sign in

Open ViewController.swift. Replace the content of signupBtnTapped(sender:) with the following:

@IBAction func signupBtnTapped(sender: AnyObject) {
  // Code to hide the keyboards for text fields
  if self.signupNameTextField.isFirstResponder() {
    self.signupNameTextField.resignFirstResponder()
  }
 
  if self.signupEmailTextField.isFirstResponder() {
    self.signupEmailTextField.resignFirstResponder()
  }
 
  if self.signupPasswordTextField.isFirstResponder() {
    self.signupPasswordTextField.resignFirstResponder()
  }
 
  // start activity indicator
  self.activityIndicatorView.hidden = false
 
  // validate presence of all required parameters
  if countElements(self.signupNameTextField.text) > 0 && countElements(self.signupEmailTextField.text) > 0 
      && countElements(self.signupPasswordTextField.text) > 0 {
    makeSignUpRequest(self.signupNameTextField.text, userEmail: self.signupEmailTextField.text, 
        userPassword: self.signupPasswordTextField.text)
  } else {
    self.displayAlertMessage("Parameters Required", alertDescription: 
        "Some of the required parameters are missing")
  }
}

The code here dismissed the keyboard for the text fields, and then validates whether the required parameters are present. It then calls makeSignUpRequest(userName:userEmail:userPassword:) to make a signup request.

Next, you’ll implement makeSignUpRequest(userName:userEmail:userPassword:). Add the following:

func makeSignUpRequest(userName:String, userEmail:String, userPassword:String) {
  // 1. Create HTTP request and set request header
  let httpRequest = httpHelper.buildRequest("signup", method: "POST", 
      authType: HTTPRequestAuthType.HTTPBasicAuth)
 
  // 2. Password is encrypted with the API key
  let encrypted_password = AESCrypt.encrypt(userPassword, password: HTTPHelper.API_AUTH_PASSWORD)
 
  // 3. Send the request Body
  httpRequest.HTTPBody = "{\"full_name\":\"\(userName)\",\"email\":\"\(userEmail)\",\"password\":\"\
      (encrypted_password)\"}".dataUsingEncoding(NSUTF8StringEncoding)
 
  // 4. Send the request
  httpHelper.sendRequest(httpRequest, completion: {(data:NSData!, error:NSError!) in
    if error != nil {
      let errorMessage = self.httpHelper.getErrorMessage(error)
      self.displayAlertMessage("Error", alertDescription: errorMessage)
 
      return
    }
 
    self.displaSigninView()
    self.displayAlertMessage("Success", alertDescription: "Account has been created")
  })
}

Here’s the section-by-section breakdown of the implementation.

  1. Uses the buildRequest(_:method:authType:) method to create an instance of NSMutableURLRequest and sets the necessary HTTP request parameters. buildRequest(_:method:authType:) is a helper method that’s implemented in the HTTPHelper struct.
  2. Encrypt the users password using AES encryption. The block uses the API password as the encryption key.
  3. Creates the JSON request body and sets all the appropriate parameters and values. For sign-up, the request requires the full name, email and encrypted password from the user.
  4. Use sendRequest(_:completion:) from the HTTPHelper struct to create an instance of NSURLSessionDataTask, which makes a request to create a new user on the Rails server. Also, the user will receive an alert when they create a new account, or the request fails.

The above method uses two helper methods which are implemented in HTTPHelper.swift

  • buildRequest(_:method:authType:)
  • sendRequest(_:completion:)

Let’s take a look at the implementations of these methods. Open HTTPHelper.swift.

buildRequest(_:method:authType:) creates an instance of NSMutableURLRequest and sets all necessary HTTP parameters for a request.

func buildRequest(path: String!, method: String, authType: HTTPRequestAuthType,
  requestContentType: HTTPRequestContentType = HTTPRequestContentType.HTTPJsonContent, requestBoundary:NSString = "") -> NSMutableURLRequest {
    // 1. Create the request URL from path
    let requestURL = NSURL(string: "\(HTTPHelper.BASE_URL)/\(path)")
    var request = NSMutableURLRequest(URL: requestURL!)
 
    // Set HTTP request method 
    request.HTTPMethod = method
 
    // 2. Set the correct Content-Type for the HTTP Request. This will be multipart/form-data for photo upload request and application/json for other requests in this app
    switch requestContentType {
    case .HTTPJsonContent:
      request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    case .HTTPMultipartContent:
      let contentType = NSString(format: "multipart/form-data; boundary=%@", requestBoundary)
      request.addValue(contentType, forHTTPHeaderField: "Content-Type")
    }
 
    // 3. Set the correct Authorization header.
    switch authType {
    case .HTTPBasicAuth:
      // Set BASIC authentication header
      let basicAuthString = "\(HTTPHelper.API_AUTH_NAME):\(HTTPHelper.API_AUTH_PASSWORD)"
      let utf8str = basicAuthString.dataUsingEncoding(NSUTF8StringEncoding)
      let base64EncodedString = utf8str?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(0))
 
      request.addValue("Basic \(base64EncodedString!)", forHTTPHeaderField: "Authorization")
    case .HTTPTokenAuth:
      // Retreieve Auth_Token from Keychain
      let userToken : NSString? = KeychainAccess.passwordForAccount("Auth_Token", service: "KeyChainService")
      // Set Authorization header
      request.addValue("Token token=\(userToken!)", forHTTPHeaderField: "Authorization")
    }
 
    return request
}
  1. Creates the instance of NSMutableURLRequest and sets the HTTP method.
  2. Sets the Content-Type to application/json or multipart/form-data. The default Content-Type is application/json, which tells the server that the request body contains JSON data.
  3. Sets the Authorization header to protect your API and user data. For Sign Up, you’re setting a HTTP Basic Authentication header. This is being passed in the 3rd parameter HTTPRequestAuthType.HTTPBasicAuth where you’re creating the HTTP request.

Basic Authentication is the first line of defense against any API attack; it combines your API username and password into a string and encodes it with Base64 to provide another layer of security.

This will deny all access to your API unless the user has the correct username and password.

Note: Despite sounding like it’s secure, Basic Authentication is not the best way secure your API as there are ways to get around it, but it’s more than sufficient in the scope of this tutorial.

sendRequest(_:completion:) creates an NSURLSession task, which is then used to send the request to the server:

func sendRequest(request: NSURLRequest, completion:(NSData!, NSError!) -> Void) -> () {
  // Create a NSURLSession task
  let session = NSURLSession.sharedSession()
  let task = session.dataTaskWithRequest(request) { (data: NSData!, response: NSURLResponse!, 
      error: NSError!) in
    if error != nil {
      dispatch_async(dispatch_get_main_queue(), { () -> Void in
        completion(data, error)
      })
 
      return
    }
 
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
      let httpResponse = response as NSHTTPURLResponse
 
      if httpResponse.statusCode == 200 {
        completion(data, nil)
      } else {
        var jsonerror:NSError?
        let errorDict = NSJSONSerialization.JSONObjectWithData(data, 
            options: NSJSONReadingOptions.AllowFragments, error:&jsonerror) as NSDictionary
 
        let responseError : NSError = NSError(domain: "HTTPHelperError", code: httpResponse.statusCode, 
            userInfo: errorDict)
        completion(data, responseError)
      }
    })
  }
 
  // start the task
  task.resume()
}

Build and run. Once the application launches, tap the Don’t have an account yet? button to create a new account.

Note: Verify your API_AUTH_NAME, API_AUTH_PASSWORD and BASE_URL are set correctly in HTTPHelper.swift if the request fails.

Selfie Signup

Next, you’ll implement the Sign in functionality, so replace the existing implementation of signinBtnTapped(sender:) with the following:

@IBAction func signinBtnTapped(sender: AnyObject) {
  // resign the keyboard for text fields
  if self.signinEmailTextField.isFirstResponder() {
    self.signinEmailTextField.resignFirstResponder()
  }
 
  if self.signinPasswordTextField.isFirstResponder() {
    self.signinPasswordTextField.resignFirstResponder()
  }
 
  // display activity indicator
  self.activityIndicatorView.hidden = false
 
  // validate presense of required parameters
  if countElements(self.signinEmailTextField.text) > 0 && 
      countElements(self.signinPasswordTextField.text) > 0 {
    makeSignInRequest(self.signinEmailTextField.text, userPassword: self.signinPasswordTextField.text)
  } else {
    self.displayAlertMessage("Parameters Required", 
        alertDescription: "Some of the required parameters are missing")
  }
}

When all the required parameters are present, the above code calls makeSignInRequest(userEmail:userPassword) to make a sign in request. Next, implement makeSignInRequest(userEmail:userPassword). Add the following:

func makeSignInRequest(userEmail:String, userPassword:String) {
  // Create HTTP request and set request Body
  let httpRequest = httpHelper.buildRequest("signin", method: "POST", 
      authType: HTTPRequestAuthType.HTTPBasicAuth)
  let encrypted_password = AESCrypt.encrypt(userPassword, password: HTTPHelper.API_AUTH_PASSWORD)
 
  httpRequest.HTTPBody = "{\"email\":\"\(self.signinEmailTextField.text)\",\"password\":\"
      \(encrypted_password)\"}".dataUsingEncoding(NSUTF8StringEncoding);
 
  httpHelper.sendRequest(httpRequest, completion: {(data:NSData!, error:NSError!) in
    // Display error
    if error != nil {
      let errorMessage = self.httpHelper.getErrorMessage(error)
      self.displayAlertMessage("Error", alertDescription: errorMessage)
 
      return
    }
 
    // hide activity indicator and update userLoggedInFlag
    self.activityIndicatorView.hidden = true
    self.updateUserLoggedInFlag()
 
    var jsonerror:NSError?
    let responseDict = NSJSONSerialization.JSONObjectWithData(data, 
        options: NSJSONReadingOptions.AllowFragments, error:&jsonerror) as NSDictionary
    var stopBool : Bool
 
    // save API AuthToken and ExpiryDate in Keychain
    self.saveApiTokenInKeychain(responseDict)
  })
}

The code above is almost identical to the implementation of the makeSignUpRequest(userName:userEmail:userPassword:) , except this time when the user successfully signs in to the application, you’ll receive an api_authtoken and authtoken_expiry date.

For all subsequent requests, you need to use the api_authtoken instead of HTTP Basic Authentication.

Implement the following methods that update the userLoggedIn flag in NSUserDefaults and save the API token respectively:

func updateUserLoggedInFlag() {
  // Update the NSUserDefaults flag
  let defaults = NSUserDefaults.standardUserDefaults()
  defaults.setObject("loggedIn", forKey: "userLoggedIn")
  defaults.synchronize()
}
 
func saveApiTokenInKeychain(tokenDict:NSDictionary) {
  // Store API AuthToken and AuthToken expiry date in KeyChain
  tokenDict.enumerateKeysAndObjectsUsingBlock({ (dictKey, dictObj, stopBool) -> Void in
    var myKey = dictKey as NSString
    var myObj = dictObj as NSString
 
    if myKey == "api_authtoken" {
      KeychainAccess.setPassword(myObj, account: "Auth_Token", service: "KeyChainService")
    }
 
    if myKey == "authtoken_expiry" {
      KeychainAccess.setPassword(myObj, account: "Auth_Token_Expiry", service: "KeyChainService")
    }
  })
 
  self.dismissViewControllerAnimated(true, completion: nil)
}

The api_authtoken is sensitive information, so you shouldn’t store it in NSUserDefaults as that would be kind of like putting out a data buffet for a would-be attacker. Hence, the code above stores it in the Keychain.

On iOS, a Keychain is an encrypted container that holds sensitive information. saveApiTokenInKeychain(tokenDict:) uses Keychain service APIs that provide ways to encrypt one or more key-value pairs.

After dismissing the current view, the application displays SelfieCollectionViewController.

Take it for a test drive! Build and run. After successfully signing in, you should see a blank view.

blank_screen

Wait, why is it blank? Shouldn’t you be able to snap a selfie or something?

There’s nothing to do just yet because you’ve not implemented the code to display anything.

Display Existing Selfies

Open SelfieCollectionViewController.swift and replace the existing implementation of viewDidAppear(_:) with the following:

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(true)
 
  // check if user is signed in
  let defaults = NSUserDefaults.standardUserDefaults()
 
  // is user is not signed in display controller to sign in or sign up
  if defaults.objectForKey("userLoggedIn") == nil {
    let loginController: ViewController = self.storyboard?.
        instantiateViewControllerWithIdentifier("ViewController") as ViewController
    self.navigationController?.presentViewController(loginController, animated: true, completion: nil)
  } else {
    // check if API token has expired
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
    let userTokenExpiryDate : NSString? = KeychainAccess.passwordForAccount("Auth_Token_Expiry", 
        service: "KeyChainService")
    let dateFromString : NSDate? = dateFormatter.dateFromString(userTokenExpiryDate!)
    let now = NSDate()
 
    let comparision = now.compare(dateFromString!)
 
    // check if should fetch new data
    if shouldFetchNewData {
      shouldFetchNewData = false
      self.setNavigationItems()
      loadSelfieData()
    }
 
    // logout and ask user to sign in again if token is expired
    if comparision != NSComparisonResult.OrderedAscending {
      self.logoutBtnTapped()
    }
  }
}

This checks if the user is signed in, and if not, or if the API token expired, then it’ll prompt the user to sign in. Otherwise, it’ll call loadSelfieData() to fetch any existing selfies.

Next, replace the contents of loadSelfieData() with the following:

func loadSelfieData () {
  // Create HTTP request and set request Body
  let httpRequest = httpHelper.buildRequest("get_photos", method: "GET", 
      authType: HTTPRequestAuthType.HTTPTokenAuth)
 
  // Send HTTP request to load existing selfie
  httpHelper.sendRequest(httpRequest, completion: {(data:NSData!, error:NSError!) in
    // Display error
    if error != nil {
      let errorMessage = self.httpHelper.getErrorMessage(error)
      let errorAlert = UIAlertView(title:"Error", message:errorMessage, delegate:nil, 
          cancelButtonTitle:"OK")
      errorAlert.show()
 
      return
    }
 
    var eror: NSError?
    let jsonDataArray = NSJSONSerialization.JSONObjectWithData(data, 
        options: NSJSONReadingOptions(0), error: &eror) as NSArray!
 
    // load the collection view with existing selfies
    if jsonDataArray != nil {
      for imageDataDict in jsonDataArray {
        var selfieImgObj = SelfieImage()
 
        selfieImgObj.imageTitle = imageDataDict.valueForKey("title") as NSString
        selfieImgObj.imageId = imageDataDict.valueForKey("random_id") as NSString
        selfieImgObj.imageThumbnailURL = imageDataDict.valueForKey("image_url") as NSString
 
        self.dataArray.append(selfieImgObj)
      }
 
      self.collectionView?.reloadData()
    }
  })
}

This code makes a GET request to fetch the user’s existing selfies.

Once the task is complete, it iterates through the array of JSON objects it’s received and updates dataArray, which will be used by the collection view cell to display images and captions.

Here instead of using HTTP Basic Authentication the buildRequest(_:method:authType:requestContentType:requestBoundary:) method uses HTTP Token Authentication. This is indicated by passing the correct authType parameter.

httpHelper.buildRequest("get_photos", method: "GET", authType: HTTPRequestAuthType.HTTPTokenAuth)

The buildRequest(_:method:authType:requestContentType:requestBoundary:) method retrieves the API auth token from the Keychain and passes it in the Authorization header.

// This is implemented in buildRequest method in HTTPHelper struct 
 
case .HTTPTokenAuth:
// Retreieve Auth_Token from Keychain
let userToken : NSString? = KeychainAccess.passwordForAccount("Auth_Token", service: "KeyChainService")
// Set Authorization header
request.addValue("Token token=\(userToken!)", forHTTPHeaderField: "Authorization")

Build and run. You should see the following screen if you’ve signed in at least once before, otherwise the application will prompt you to sign in.

Logout

Since you’ve signed in at least once, the application remembers the API auth_token for the user and sets the necessary flag in NSUserDefaults. Hence, it displays SelfieViewController without prompting you to login again.

Next, replace the content of collectionView(_:cellForItemAtIndexPath:) inside SelfieCollectionViewController.swift with the following code:

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, 
      forIndexPath: indexPath) as SelfieCollectionViewCell
 
  // Configure the cell
  var rowIndex = self.dataArray.count - (indexPath.row + 1)
  var selfieRowObj = self.dataArray[rowIndex] as SelfieImage
 
  cell.backgroundColor = UIColor.blackColor()
  cell.selfieTitle.text = selfieRowObj.imageTitle
 
  var imgURL: NSURL = NSURL(string: selfieRowObj.imageThumbnailURL)!
 
  // Download an NSData representation of the image at the URL
  let request: NSURLRequest = NSURLRequest(URL: imgURL)
  NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), 
      completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
    if error == nil {
      var image = UIImage(data: data)
 
      dispatch_async(dispatch_get_main_queue(), {
        cell.selfieImgView.image = image
      })
    } else {
      println("Error: \(error.localizedDescription)")
    }
  })
 
  return cell
}

The rowIndex displays the most recent selfie on top and puts the older ones below. This also sets the title and image of any individual collection view cell, then it downloads a remote image asynchronously without blocking the main thread.

You’ve just implemented the code to display existing selfies for the user, but you still need a selfie to work with here!

Uploading a Selfie to the Server

When the user taps the camera icon on the navigation bar, it calls cameraBtnTapped(_:), which in turn calls displayCameraControl() to bring up the image picker controller.

In SelfieCollectionViewController.swift locate imagePickerController(_:didFinishPickingMediaWithInfo:) and replace it with the following code:

func imagePickerController(picker: UIImagePickerController!, didFinishPickingMediaWithInfo info: NSDictionary!) {
  // dismiss the image picker controller window
  self.dismissViewControllerAnimated(true, completion: nil)
 
  var image:UIImage!
 
  // fetch the selected image
  if picker.allowsEditing {
    image = info.objectForKey(UIImagePickerControllerEditedImage) as UIImage
  } else {
    image = info.objectForKey(UIImagePickerControllerOriginalImage) as UIImage
  }
 
  presentComposeViewControllerWithImage(image)
}

In the above code snippet, you’re extracting the selected image and calling presentComposeViewControllerWithImage(_:) with the selected image. imagePickerController(_:didFinishPickingMediaWithInfo:) gets called when the user selects an image.

Implement presentComposeViewControllerWithImage(_:) now by adding the following:

func presentComposeViewControllerWithImage(image:UIImage!) {
  // instantiate compose view controller to capture a caption
  let composeVC: ComposeViewController = self.storyboard?.
      instantiateViewControllerWithIdentifier("ComposeViewController") as ComposeViewController
  composeVC.composeDelegate = self
  composeVC.thumbImg = image
 
  // set the navigation controller of compose view controlle
  let composeNavVC = UINavigationController(rootViewController: composeVC)
 
  // present compose view controller
  self.navigationController?.presentViewController(composeNavVC, animated: true, completion: nil)
}

presentComposeViewControllerWithImage(_:) instantiates and presents the compose view controller where you’ll prompt the user to add a caption to the image.

In SelfieCollectionViewController.swift you’ll notice few extensions. These extensions conform to specific protocols and keep the related methods grouped together with the protocol. For example, camera extension groups the methods that are responsible to display the camera control and manages the image picker delegate methods.

Open ComposeViewController.swift and replace viewDidLoad() with the following:

override func viewDidLoad() {
  super.viewDidLoad()
 
  // Do any additional setup after loading the view.
  self.titleTextView.becomeFirstResponder()
  self.thumbImgView.image = thumbImg
  self.automaticallyAdjustsScrollViewInsets = false
  self.activityIndicatorView.layer.cornerRadius = 10
 
  setNavigationItems()
}

This uses the same image the user selects as a thumbnail image and asks user to enter a caption.

Next, replace the contents of postBtnTapped() with the following code:

func postBtnTapped() {
  // resign the keyboard for text view
  self.titleTextView.resignFirstResponder()
  self.activityIndicatorView.hidden = false
 
  // Create Multipart Upload request
  var imgData : NSData = UIImagePNGRepresentation(thumbImg)
  let httpRequest = httpHelper.uploadRequest("upload_photo", data: imgData, title: self.titleTextView.text)
 
  httpHelper.sendRequest(httpRequest, completion: {(data:NSData!, error:NSError!) in
    // Display error
    if error != nil {
      let errorMessage = self.httpHelper.getErrorMessage(error)
      self.displayAlertMessage("Error", alertDescription: errorMessage)
 
      return
    }
 
    var eror: NSError?
    let jsonDataDict = NSJSONSerialization.JSONObjectWithData(data, 
        options: NSJSONReadingOptions(0), error: &eror) as NSDictionary
 
    var selfieImgObjNew = SelfieImage()
 
    selfieImgObjNew.imageTitle = jsonDataDict.valueForKey("title") as NSString
    selfieImgObjNew.imageId = jsonDataDict.valueForKey("random_id") as NSString
    selfieImgObjNew.imageThumbnailURL = jsonDataDict.valueForKey("image_url") as NSString
 
    self.composeDelegate.reloadCollectionViewWithSelfie(selfieImgObjNew)
    self.activityIndicatorView.hidden = true
    self.dismissViewControllerAnimated(true, completion: nil)
  })
}

The above code uses uploadRequest(_:data:title:) method instead of buildRequest(_:method:authType:requestContentType:requestBoundary:) to create the request.

If you take a look at uploadRequest(path:data:title:) method in HTTPHelper.swift, you’ll notice the implementation is a little different from buildRequest(_:method:authType:requestContentType:requestBoundary:). So take a moment to understand it before you move ahead.

func uploadRequest(path: String, data: NSData, title: NSString) -> NSMutableURLRequest {
  let boundary : NSString = "---------------------------14737809831466499882746641449"
  var request = buildRequest(path, method: "POST", authType: HTTPRequestAuthType.HTTPTokenAuth,
      requestContentType:HTTPRequestContentType.HTTPMultipartContent, 
      requestBoundary:boundary) as NSMutableURLRequest
 
  let bodyParams : NSMutableData = NSMutableData()
 
  // build and format HTTP body with data
  // prepare for multipart form uplaod
 
  let boundaryString = NSString(format: "--%@\r\n",boundary)
  let boundaryData = boundaryString.dataUsingEncoding(NSUTF8StringEncoding) as NSData!
  bodyParams.appendData(boundaryData)
 
  // set the parameter name
  let imageMeteData = "Content-Disposition: attachment; name=\"image\"; 
      filename=\"photo\"\r\n".dataUsingEncoding(NSUTF8StringEncoding, 
      allowLossyConversion: false)
  bodyParams.appendData(imageMeteData!)
 
  // set the content type
  let fileContentType = "Content-Type: application/octet-stream\r\n\r\n".
      dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
  bodyParams.appendData(fileContentType!)
 
  // add the actual image data
  bodyParams.appendData(data)
 
  let imageDataEnding = "\r\n".dataUsingEncoding(NSUTF8StringEncoding, 
      allowLossyConversion: false)
  bodyParams.appendData(imageDataEnding!)
 
  let boundaryString2 = NSString(format: "--%@\r\n",boundary)
  let boundaryData2 = boundaryString.dataUsingEncoding(NSUTF8StringEncoding) as NSData!
 
  bodyParams.appendData(boundaryData2)
 
  // pass the caption of the image
  let formData = "Content-Disposition: form-data; name=\"title\"\r\n\r\n".
      dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
  bodyParams.appendData(formData!)
 
  let formData2 = title.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
  bodyParams.appendData(formData2!)
 
  let closingFormData = "\r\n".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
  bodyParams.appendData(closingFormData!)
 
  let closingData = NSString(format: "--%@--\r\n",boundary)
  let boundaryDataEnd = closingData.dataUsingEncoding(NSUTF8StringEncoding) as NSData!
 
  bodyParams.appendData(boundaryDataEnd)
 
  request.HTTPBody = bodyParams
  return request
}

The initial part should be familiar — the part where it uses buildRequest(_:method:authType:requestContentType:requestBoundary:) to create an instance of NSMutableURLRequest and sets an Authorization header. However, there are 2 additional parameters it passes to buildRequest(_:method:authType:requestContentType:requestBoundary:) method.

buildRequest(path, method: "POST", authType: HTTPRequestAuthType.HTTPTokenAuth, requestContentType:HTTPRequestContentType.HTTPMultipartContent, requestBoundary:boundary) as NSMutableURLRequest
  1. requestContentType:HTTPRequestContentType.HTTPMultipartContent
  2. requestBoundary:boundary

The Content-Type used here is different from the Content-Type that you passed in other requests. Instead of application/json you passed multipart/form-data which tells the server the request body is a series of parts. And each part is separated by a boundary.

That’s why in next few lines of code you’ll notice the boundary multiple times.

Usually a server delimits request parameters and value combinations by &. However, for uploading images, where you send the actual binary data, there might be one or more & in the data itself, so with the help of the boundary, it knows how to split the data that’s being sent.

Open SelfieCollectionViewController.swift and update reloadCollectionViewWithSelfie(_:) with the following:

func reloadCollectionViewWithSelfie(selfieImgObject: SelfieImage) {
  self.dataArray.append(selfieImgObject)
  self.collectionView?.reloadData()
}

This updates the dataArray and reloads the collection view

Build and run. Upload your first selfie, and make it a good one! :]

Note: If you’re using Simulator, you can select an image from the photo album. But if it’s empty, open Safari on the simulator, use Google to find an appropriate image, then hold and press the mouse button to invoke the context menu, and then save the image.

Photo Upload

Looks great! Well, go ahead and upload a few more — you’ll want a nice little collection. :]

If this didn’t work for you, then go back and double check that you followed all the steps to generate your Amazon S3 credentials and that the Heroku configuration is also correct.

Deleting a Selfie

What if the user thinks their nose looks a little too shiny or they notice there’s something in their teeth? You need to provide a way for them to wipe that humiliating shot from existence.

Open SelfieCollectionViewController.swift and replace collectionView(_:didSelectItemAtIndexPath:) with the following:

override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
  // fetch the Selfie Image Object
  var rowIndex = self.dataArray.count - (indexPath.row + 1)
  var selfieRowObj = self.dataArray[rowIndex] as SelfieImage
 
  pushDetailsViewControllerWithSelfieObject(selfieRowObj)
}

This calls pushDetailsViewControllerWithSelfieObject(_:) passing a SelfieImage object. Implement pushDetailsViewControllerWithSelfieObject(_:) by adding the following:

func pushDetailsViewControllerWithSelfieObject(selfieRowObj:SelfieImage!) {
  // instantiate detail view controller
  let detailVC = self.storyboard?.instantiateViewControllerWithIdentifier("DetailViewController") as 
      DetailViewController
  detailVC.editDelegate = self
  detailVC.selfieCustomObj = selfieRowObj
 
  // push detail view controller to the navigation stack
  self.navigationController?.pushViewController(detailVC, animated: true)
}

The above instantiates DetailViewController from the storyboard and sets the selfie image object. When the user taps on a selfie, DetailViewController is displayed. Here the user can check out their selfie, and delete it if they don’t like it.

Open DetailViewController.swift and replace the viewDidLoad() with the following:

override func viewDidLoad() {
  super.viewDidLoad()
 
  self.activityIndicatorView.layer.cornerRadius = 10
  self.detailTitleLbl.text = self.selfieCustomObj.imageTitle
  var imgURL = NSURL(string: self.selfieCustomObj.imageThumbnailURL)
 
  // Download an NSData representation of the image at the URL
  let request = NSURLRequest(URL: imgURL!)
 
  NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), 
      completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
    if error == nil {
      var image = UIImage(data: data)
 
      dispatch_async(dispatch_get_main_queue(), {
        self.detailThumbImgView.image = image
      })
    } else {
      println("Error: \(error.localizedDescription)")
    }
  })
}

This sets the title of the image in detail view and asynchronously downloads the selfie from Amazon S3 before displaying it. When the user deletes an image, the action triggers deleteBtnTapped(_:).

Replace deleteBtnTapped(_:) with the following:

@IBAction func deleteBtnTapped(sender: AnyObject) {
  // show activity indicator
  self.activityIndicatorView.hidden = false
 
  // Create HTTP request and set request Body
  let httpRequest = httpHelper.buildRequest("delete_photo", method: "DELETE", 
      authType: HTTPRequestAuthType.HTTPTokenAuth)
  httpRequest.HTTPBody = "{\"photo_id\":\"\(self.selfieCustomObj.imageId)\"}".
      dataUsingEncoding(NSUTF8StringEncoding);
 
  httpHelper.sendRequest(httpRequest, completion: {(data:NSData!, error:NSError!) in
    // Display error
    if error != nil {
      let errorMessage = self.httpHelper.getErrorMessage(error)
      self.displayAlertMessage("Error", alertDescription: errorMessage)
 
      return
    }
 
    self.editDelegate.deleteSelfieObjectFromList(self.selfieCustomObj)
    self.activityIndicatorView.hidden = true
    self.navigationController?.popToRootViewControllerAnimated(true)
  })
}

The above creates an HTTP DELETE request to delete the selfie from the server. Upon successful completion, the above code also calls the method deleteSelfieObjectFromList(_:), which deletes the selfie from the local list of selfies and updates the collection view.

Open SelfieCollectionViewController.swift and add the following two methods:

// This is in the base SelfieCollectionViewController class implementation
func removeObject<T:Equatable>(inout arr:Array<T>, object:T) -> T? {
  if let indexOfObject = find(arr,object) {
    return arr.removeAtIndex(indexOfObject)
  }
  return nil
}
// This is in edit selfie extension
func deleteSelfieObjectFromList(selfieImgObject: SelfieImage) {
  if contains(self.dataArray, selfieImgObject) {
    removeObject(&self.dataArray, object: selfieImgObject)
    self.collectionView?.reloadData()
  }
}

The first method deletes an object from an array, and the second method is the protocol implementation that deletes a local selfie and reloads the collection view.

Build and run. Delete your least favorite selfie — And just like that, it’s a distant memory. Good thing for Mr. Panda — ever since he became synonymous with SEO, he’s been rather particular about his selfies.

delete

Handling Signing Out

Open SelfieCollectionViewController.swift and replace the contents of logoutBtnTapped() with the following:

func logoutBtnTapped() {
  clearLoggedinFlagInUserDefaults()
  clearDataArrayAndReloadCollectionView()
  clearAPITokensFromKeyChain()
 
  // Set flag to display Sign In view
  shouldFetchNewData = true
  self.viewDidAppear(true)
}

Next, implement the following three methods that get called from logoutBtnTapped

// 1. Clears the NSUserDefaults flag
func clearLoggedinFlagInUserDefaults() {
  let defaults = NSUserDefaults.standardUserDefaults()
  defaults.removeObjectForKey("userLoggedIn")
  defaults.synchronize()
}
 
// 2. Removes the data array
func clearDataArrayAndReloadCollectionView() {
  self.dataArray.removeAll(keepCapacity: true)
  self.collectionView?.reloadData()
}
 
// 3. Clears API Auth token from Keychain
func clearAPITokensFromKeyChain () {
  // clear API Auth Token
  if let userToken = KeychainAccess.passwordForAccount("Auth_Token", service: "KeyChainService") {
    KeychainAccess.deletePasswordForAccount(userToken, account: "Auth_Token", service: "KeyChainService")
  }
 
  // clear API Auth Expiry
  if let userTokenExpiryDate = KeychainAccess.passwordForAccount("Auth_Token_Expiry", 
      service: "KeyChainService") {
    KeychainAccess.deletePasswordForAccount(userTokenExpiryDate, account: "Auth_Token_Expiry", 
        service: "KeyChainService")
  }
}

The above methods carry out the following tasks:

  1. Removes userLoggedIn flag from NSUserDefaults.
  2. Removes the data array and reloads the collection view. This is to make sure that when a new user signs in they don’t see any cached data.
  3. Clears the API auth token and credentials from the Keychain.

The logoutBtnTapped() also gets triggered when the API auth token expires, forcing the user to sign in again and obtain a new auth token.

Build and run. Tap Logout; you should be taken back to the Sign In screen.

Where To Go From Here?

Here’s the finished sample project with all the code from tutorial.

Congratulations! You’ve just successfully set up a backend server on Heroku that provides your API, configured an Amazon S3 bucket to store your users’ selfie images, and built an application that allows a user to upload a selfie to your service.

No doubt this absolutely essential app will help you capture your moods, snapping those awesome moments like this one right now. You did take a selfie of your victory face, right?

Take a look at the Authentication Cheat Sheet by OWASP; it’s also a good resource for many other security guides and materials that you can access for free.

Thank you for taking the time to work through this tutorial! If you have any questions, comments, or find a unique requirement about secure API design or mobile security, feel free to chime in below and I will be happy to help.

User Accounts on iOS with Ruby on Rails and Swift is a post from: Ray Wenderlich

The post User Accounts on iOS with Ruby on Rails and Swift appeared first on Ray Wenderlich.

Video Tutorial: iOS 101 with Swift Part 4: Storyboards And Segues


Video Tutorial: iOS 101 with Swift Part 5: Navigation View Controllers

Video Tutorial: iOS 101 with Swift Part 6: Tab Bar View Controllers

Video Tutorial: iOS 101 with Swift Part 7: Container View Controllers

Video Tutorial: Swift Scroll View School Part 0: Introduction

Video Tutorial: Swift Scroll View School Part 1: Frame and Bounds

Viewing all 4373 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>