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

Beginning Core Image in Swift

$
0
0

CoreImage_non_feast

Update note: This tutorial was updated for iOS 8 and Swift by Nick Lockwood. Original post by Tutorial Team member Jake Gunderson.

Core Image is a powerful framework that lets you easily apply filters to images. You can get all kinds of effects, such as modifying the vibrance, hue, or exposure. It can use either the CPU or GPU to process the image data and is very fast — fast enough to do real-time processing of video frames!

Core Image filters can also be chained together to apply multiple effects to an image or video frame at once. The multiple filters are combined into a single filter that is applied to the image. This makes it very efficient compared to processing the image through each filter, one at a time.

In this tutorial, you will get hands-on experience playing around with Core Image. You’ll apply a few different filters, and you’ll see how easy it is to apply cool effects to images in real time!

Getting Started

Before you get started, let’s discuss some of the most important classes in the Core Image framework:

  • CIContext. All of the processing of a core image is done in a CIContext. This is somewhat similar to a Core Graphics or OpenGL context.
  • CIImage. This class hold the image data. It can be created from a UIImage, from an image file, or from pixel data.
  • CIFilter. The CIFilter class has a dictionary that defines the attributes of the particular filter that it represents. Examples of filters are vibrance, color inversion, cropping, and many more.

You’ll be using each of these classes in this project.

CoreImageFun

Open up Xcode and create a new project with the iOS \ Application \ Single View Application template. Enter CoreImageFun for the Product Name, select iPhone for the Devices option, and make sure that Language is set to Swift.

Download the resources for this tutorial, and add the included image.png to your project.

Next, open Main.storyboard, and drag an image view in as a subview of the existing view. In the Attributes Inspector, set the image view’s content mode to Aspect Fit, so it won’t distort images.

Next, ensure the Document Outline (the hierarchy left of the canvas in Interface Builder) is visible – you can enable it from the menu at Editor \ Show Document Outline.

Control-drag from the image view to its superview three times to add three constraints:

  1. Add a constraint Top Space to Layout Guide, using the Size Inspector to set the constraint’s constant to zero if necessary.
  2. Add a constraint to Center Horizontally in Container (also setting its constant to zero).
  3. Add an Equal Width constraint.

Finally, to constrain the image view’s height, control drag from the image view to itself, and add an Aspect Ratio constraint, using the Size Inspector to set its multiplier 8:5 for the ratio of width to height and a constant factor of zero. Finally, navigate to Editor \ Resolve Auto Layout Issues \ All Views in View Controller \ Update Frames, so Interface Builder updates the layout based on these new constraints.

Next, open the Assistant Editor and make sure it’s displaying ViewController.swift. Control-drag from the UIImageView to just after the opening brace of the ViewController class. Name the outlet imageView, and click Connect.

Build and run the project just to make sure everything is good so far – you should just see an empty screen. The initial setup is complete – now onto Core Image!

Basic Image Filtering

You’re going to get started by simply running your image through a CIFilter and displaying it on the screen. Every time you want to apply a CIFilter to an image you need to do four things:

  1. Create a CIImage object. CIImage has several initialization methods, including: CIImage(contentsOfURL:), CIImage(data:), CIImage(CGImage:), CIImage(bitmapData:bytesPerRow:size:format:colorSpace:), and several others. You’ll most likely be working with CIImage(contentsOfURL:) most of the time.
  2. Create a CIContext. A CIContext can be CPU or GPU based. A CIContext is relatively expensive to initialize so you reuse it rather than create it over and over. You will always need one when outputting the CIImage object.
  3. Create a CIFilter. When you create the filter, you configure a number of properties on it that depend on the filter you’re using.
  4. Get the filter output. The filter gives you an output image as a CIImage – you can convert this to a UIImage using the CIContext, as you’ll see below.

Let’s see how this works. Add the following code to ViewController.swift inside viewDidLoad():

// 1
let fileURL = NSBundle.mainBundle().URLForResource("image", withExtension: "png")
 
// 2
let beginImage = CIImage(contentsOfURL: fileURL)
 
// 3
let filter = CIFilter(name: "CISepiaTone")
filter.setValue(beginImage, forKey: kCIInputImageKey)
filter.setValue(0.5, forKey: kCIInputIntensityKey)
 
// 4
let newImage = UIImage(CIImage: outputImage)
self.imageView.image = newImage

Let’s go over this section by section:

  1. This line creates an NSURL object that holds the path to your image file.
  2. Next you create your CIImage with the CIImage(contentsOfURL:) constructor.
  3. Next you’ll create your CIFilter object. The CIFilter constructor takes the name of the filter, and a dictionary that specifies the keys and values for that filter. Each filter will have its own unique keys and set of valid values. The CISepiaTone filter takes only two values, the KCIInputImageKey (a CIImage) and the kCIInputIntensityKey, a float value between 0 and 1. Here you give that value 0.5. Most of the filters have default values that will be used if no values are supplied. One exception is the CIImage, this must be provided as there is no default.
  4. Getting a CIImage back out of a filter is as easy as using the outputImage property. Once you have an output CIImage, you will need to convert it into a UIImage. The UIImage(CIImage:) constructor creates a UIImage from a CIImage. Once you’ve converted it to a UIImage, you just display it in the image view you added earlier.

Build and run the project, and you’ll see your image filtered by the sepia tone filter.

CI-Sepia-Crop

Congratulations, you have successfully used CIImage and CIFilters!

Putting It Into Context

Before you move forward, there’s an optimization that you should know about.

I mentioned earlier that you need a CIContext in order to apply a CIFilter, yet there’s no mention of this object in the above example. It turns out that the the UIImage(CIImage:) constructor does all the work for you. It creates a CIContext and uses it to perform the work of filtering the image. This makes using the Core Image API very easy.

There is one major drawback – it creates a new CIContext every time it’s used. CIContext instances are meant to be reusable to increase performance. If you want to use a slider to update the filter value, as you’ll be doing in this tutorial, creating a new CIContext each time you change the filter would be way too slow.

Let’s do this properly. Delete step 4 from the code you added to viewDidLoad(), and replace it with the following:

// 1
let context = CIContext(options:nil)
 
// 2
let cgimg = context.createCGImage(outputImage, fromRect: outputImage.extent())
 
// 3
let newImage = UIImage(CGImage: cgimg)
self.imageView.image = newImage

Again, let’s go over this section by section.

  1. Here you set up the CIContext object and use it to draw a CGImage. The CIContext(options:) constructor takes an NSDictionary that specifies options such as the color format, or whether the context should run on the CPU or GPU. For this app, the default values are fine and so you pass in nil for that argument.
  2. Calling createCGImage(outputImage:fromRect:) on the context with the supplied CIImage will return a new CGImage instance.
  3. Next, you use the UIImage(CGImage:) constructor to create a UIImage from the newly created CGImage instead of directly from the CIImage as before. Note that there is no need to explicitly release the CGImage after we are finished with it, as there would have been in Objective-C. In Swift, ARC can automatically release Core Foundation objects.

Build and run, and make sure it works just as before.

In this example, handling the CIContext creation yourself doesn’t make much difference. But in the next section, you’ll see why this is important for performance, as you implement the ability to modify the filter dynamically!

Changing Filter Values

This is great, but it’s just the beginning of what you can do with Core Image filters. Lets add a slider and set it up so you can adjust the filter settings in real time.

Open Main.storyboard, and drag in a slider, and drop it in below the image view, centered horizontally. With the view selected, navigate to Editor \ Resolve Auto Layout Issues \ Selected Views \ Reset to Suggested Constraints, increasing the width constraint if desired.

Make sure the Assistant Editor is visible and displaying ViewController.swift, then control-drag from the slider down below the previously added @IBOutlet, set the name to amountSlider, and click Connect.

While you’re at it let’s connect the slider to an action method as well. Again control-drag from the slider, this time to just above the closing } of the ViewController class. Set the Connection to Action, the name to amountSliderValueChanged, make sure that the Event is set to Value Changed, and click Connect.

Every time the slider changes, you need to redo the image filter with a different value. However, you don’t want to redo the whole process, that would be very inefficient and would take too long. You’ll need to change a few things in your class so that you hold on to some of the objects you create in your viewDidLoad method.

The biggest thing you want to do is reuse the CIContext whenever you need to use it. If you recreate it each time, your program will run very slowly. The other things you can hold onto are the CIFilter and the CIImage that holds your original image. You’ll need a new CIImage for every output, but the image you start with will stay constant.

You need to add some instance variables to accomplish this task. Add the following three properties to your ViewController class:

var context: CIContext!
var filter: CIFilter!
var beginImage: CIImage!

Note that you have declared these values as implicitly-unwrapped optionals using the ! syntax, because you aren’t going to initialize them until viewDidLoad. You could have used ? instead, but you know that the way the code is designed will prevent the optionals from being nil by the time you use them. The implicitly-unwrapped syntax makes it much easier to read, without all the exclamation marks everywhere.

Change the code in viewDidLoad so it uses these properties instead of declaring new local variables, as follows:

beginImage = CIImage(contentsOfURL: fileURL)
 
filter = CIFilter(name: "CISepiaTone")
filter.setValue(beginImage, forKey: kCIInputImageKey)
filter.setValue(0.5, forKey: kCIInputIntensityKey)
 
let outputImage = filter.outputImage
 
context = CIContext(options:nil)
let cgimg = context.createCGImage(outputImage, fromRect: outputImage.extent())

Now you’ll implement the changeValue method. What you’ll be doing in this method is altering the value of the inputIntensity key in your CIFilter dictionary.

Once you’ve altered this value you’ll need to repeat a few steps:

  • Get the output CIImage from the CIFilter.
  • Convert the CIImage to a CGImage.
  • Convert the CGImage to a UIImage, and display it in the image view.

Replace amountSliderValueChanged(sender:) with the following:

@IBAction func amountSliderValueChanged(sender: UISlider) {
 
    let sliderValue = sender.value
 
    filter.setValue(sliderValue, forKey: kCIInputIntensityKey)
    let outputImage = filter.outputImage
 
    let cgimg = context.createCGImage(outputImage, fromRect: outputImage.extent())
 
    let newImage = UIImage(CGImage: cgimg)
    self.imageView.image = newImage
}

You’ll notice that you’ve changed the argument type from AnyObject to UISlider in the method definition. You know you’ll only be using this method to retrieve values from your UISlider, so you can go ahead and make this change. If you’d left it as AnyObject, you’d need to cast it to a UISlider or the next line would throw an error.

You retrieve the value from the slider (which returns a Float). Your slider is set to the default values – min 0, max 0, default 0.5. How convenient, these happen to be the right values for this CIFilter!

The CIFilter has methods that will allow you to set the values for the different keys in its dictionary. Here, you’re just setting the inputIntensity to whatever you get from your slider. Swift automatically converts the primitive CFloat value into an NSNumber object suitable for use with setValue(value:forKey:).

The rest of the code should look familiar, as it follows the same logic as viewDidLoad. You’re going to be using this code over and over again. From now on, you’ll use amountSliderValueChanged(sender:) to render the output of a CIFilter to your UIImageView.

Build and run, and you should have a functioning live slider that will alter the sepia value for your image in real time!

Getting Photos from the Photo Album

Now that you can change the values of the filter on the fly, things are starting to get real interesting! But what if you don’t care for this image of flowers? Next you’ll set up a UIImagePickerController so you can get pictures from out of the photo album and into your app so you can play with them.

You need to create a button that will bring up the photo album view, so open up Main.storyboard, drag in a button to the bottom right of the scene, and label it “Photo Album”. As before, use Auto Layout to Reset to Suggested Constraints. The button should be underneath the slider on the right side.

Make sure the Assistant Editor is visible and displaying ViewController.swift, then control-drag from the button down to just above the closing } for the ViewController class. Set the Connection to Action, the name to loadPhoto, make sure that the Event is set to Touch Up Inside, and click Connect.

Implement the loadPhoto method as follows:

@IBAction func loadPhoto(sender : AnyObject) {
  let pickerC = UIImagePickerController()
  pickerC.delegate = self
  self.presentViewController(pickerC, animated: true, completion: nil)
}

The first line of code instantiates a new UIImagePickerController. You then set the delegate of the image picker to self (the ViewController).

You get a warning here. You need to declare that ViewController conforms to the UIImagePickerControllerDelegate and UINavigationControllerDelegate protocols.

Still in ViewController.swift, change the class definition at the top of the file as follows:

class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {

Now implement the following method:

func imagePickerController(picker: UIImagePickerController!, didFinishPickingMediaWithInfo info: NSDictionary!) {
  self.dismissViewControllerAnimated(true, completion: nil);
  println(info);
}

This UIImagePickerControllerDelegate method isn’t completed yet – it’s just a placeholder to log some information about the chosen image. Note that whenever you implement any of the UIImagePickerControllerDelegate methods, you have to dismiss the UIImagePickerController explicitly in your implementation. If you don’t, then you’ll just stare at the image picker forever!

Build and run the app, and tap the button. It will bring up the image picker with the photos in your photo album.

If you are running this in the simulator, you probably won’t have any photos. On the simulator (or on a device without a camera), you can use Safari to save images to your photo album. Open Safari, find an image, tap and hold, and you’ll get an option to save that image. Next time you run your app, it will appear in your photo library.

Here’s what you should see in the console after you’ve selected an image (something like this):

{
UIImagePickerControllerMediaType = "public.image";
UIImagePickerControllerOriginalImage = " size {1165, 770} orientation 0 scale 1.000000";
UIImagePickerControllerReferenceURL = "assets-library://asset/asset.PNG?id=DCFE1435-2C01-4820-9182-40A69B48EA67&ext=PNG";
}

Note that it has an entry in the dictionary for the “original image” selected by the user. This is what you want to pull out and filter!

Now that you’ve got a way to select an image, how do you use that as your beginImage?

Simple, just update the delegate method to look like this:

func imagePickerController(picker: UIImagePickerController!, didFinishPickingMediaWithInfo info: NSDictionary!) {
  self.dismissViewControllerAnimated(true, completion: nil);
 
  let gotImage = info[UIImagePickerControllerOriginalImage] as UIImage
 
  beginImage = CIImage(image:gotImage)
  filter.setValue(beginImage, forKey: kCIInputImageKey)
  self.amountSliderValueChanged(amountSlider)
}

You need to create a new CIImage from your selected photo. You can get the UIImage representation of the photo by finding it in the dictionary of values, under the UIImagePickerControllerOriginalImage key constant. Note that it’s better to use a constant for this, rather than a hardcoded string, because Apple could change the name of the key in the future.

You need to convert the image into a CIImage object with the CIImage(image:) constructor. You then set the key in the filter dictionary so that the input image is the new CIImage you just created.

The last line may seem odd. Remember how I pointed out that the code in changeValue ran the filter with the latest value and updated the image view with the result?

Well you need to do that again, so you can just call changeValue. Even though the slider value hasn’t changed, you can still use that method’s code to get the job done. You could break that code into its own method (and if you were going to be working with more complexity, you would, to avoid confusion), but in this case your purpose is served by re-using the amountSliderValueChanged method. Pass in the amountSlider as the sender so that it has the correct value to use.

Build and run, and now you’ll be able to update any image from your photo album!

What if you create the perfect sepia image, how do you hold on to it? You could take a screenshot, but the proper way is to save the filtered photo back into the photo album.

Saving to Photo Album

To save to the photo album, you need to use the AssetsLibrary framework. Add the following import statement to the top of ViewController.swift:

import AssetsLibrary

One thing you should know is that when you save a photo to the album, it may take a few seconds, and that process could continue even after you close the app.

This could be a problem, as the GPU stops whatever it’s doing when you switch from one app to another. If the photo hasn’t finished saving, it won’t be there when you go looking for it later!

The solution to this is to use a CPU-based CIContext for rendering. The default behavior is to use the GPU because it’s much faster, and you don’t want to slow down the filtering performance for the sake of adding save functionality. Instead, you will create a second CIContext to use for saving the image. Note that the software renderer won’t work properly in the simulator.

Add a new button to your app that will let you save the photo you are currently modifying with all the changes you’ve made. Open Main.storyboard, add a new button labeled “Save to Album”. Place the button under the slider on the left side, and add the suggested constraints.

Then connect it to a new savePhoto(sender:) method, like you did last time, and implement the method as follows:

@IBAction func savePhoto(sender: AnyObject) {
    // 1
    let imageToSave = filter.outputImage
 
    // 2
    let softwareContext = CIContext(options:[kCIContextUseSoftwareRenderer: true])
 
    // 3
    let cgimg = softwareContext.createCGImage(imageToSave, fromRect:imageToSave.extent())
 
    // 4
    let library = ALAssetsLibrary()
    library.writeImageToSavedPhotosAlbum(cgimg,
      metadata:imageToSave.properties(),
      completionBlock:nil)
}

In this code block you:

  1. Get the CIImage output from the filter.
  2. Create a new, software based CIContext that uses the CPU renderer.
  3. Generate the CGImage.
  4. Save the CGImage to the photo library.

Build and run the app (remember to run on an actual device, since you’re using software rendering), and now you can save that “perfect image” to your photo library so it’s preserved forever!

What About Image Metadata?

Let’s talk about image metadata for a moment. Image files taken on mobile phones have a variety of data associated with them, such as GPS coordinates, image format, and orientation.

Orientation in particular is something that you’ll need to preserve. The process of loading a UIImage into a CIImage, rendering to a CGImage, and converting back to a UIImage strips the metadata from the image. In order to preserve orientation, you’ll need to record it and then pass it back into the UIImage constructor.

Start by adding a new property inside the ViewController class definition:

var orientation: UIImageOrientation = .Up

Next, add the following line to imagePickerController(picker:didFinishPickingMediaWithInfo:) just before setting beginImage:

orientation = gotImage.imageOrientation

This will save the original image orientation to the property.

Finally, alter the line in amountSliderValueChanged that creates the UIImage that you set to the imageView object:

let newImage = UIImage(CGImage: cgimg, scale:1, orientation:orientation)

Now, if you take a picture taken in something other than the default orientation, it will be preserved.

What Other Filters are Available?

The CIFilter API has more than 160 filters on Mac OS, 126 of which are available on iOS 8. As of iOS 8, it is now possible to create you own custom filters as well.

In order to find out what filters are available on a given device, you can use the CIFilter method filterNamesInCategory(kCICategoryBuiltIn). This method will return an array of filter names.

In addition, each filter has an attributes() method that will return a dictionary containing information about that filter. This information includes the filter’s name and category, the kinds of inputs the filter takes, and the default and acceptable values for those inputs.

Let’s put together a method for your class that logs the information for all the currently available filters. Add this method inside the ViewController class definition:

func logAllFilters() {
  let properties = CIFilter.filterNamesInCategory(kCICategoryBuiltIn)
  println(properties)
 
  for filterName: AnyObject in properties {
    let fltr = CIFilter(name:filterName as String)
    println(fltr.attributes())
  }
}

This method simply gets the array of filters from filterNamesInCategory(). It prints the list of names first. Then, for each name in the list, it instantiates the filter and logs its attributes dictionary.

Call this method at the end of viewDidLoad():

self.logAllFilters()

You will see the many filters listed in the console like the following:

[CIAttributeFilterDisplayName: Color Monochrome, inputColor: {
    CIAttributeClass = CIColor;
    CIAttributeDefault = "(0.6 0.45 0.3 1)";
    CIAttributeType = CIAttributeTypeColor;
}, inputImage: {
    CIAttributeClass = CIImage;
    CIAttributeType = CIAttributeTypeImage;
}, CIAttributeFilterCategories: (
    CICategoryColorEffect,
    CICategoryVideo,
    CICategoryInterlaced,
    CICategoryNonSquarePixels,
    CICategoryStillImage,
    CICategoryBuiltIn
), inputIntensity: {
    CIAttributeClass = NSNumber;
    CIAttributeDefault = 1;
    CIAttributeIdentity = 0;
    CIAttributeSliderMax = 1;
    CIAttributeSliderMin = 0;
    CIAttributeType = CIAttributeTypeScalar;
}, CIAttributeFilterName: CIColorMonochrome]

Wow, that’s a lot of filters! That should give you some ideas for other filters to try out in your own app!

More Intricate Filter Chains

Now that we’ve looked at all the filters that are available on the iOS platform, it’s time to create a more intricate filter chain. In order to do this, you’ll create a dedicated method to process the CIImage. It will take a CIImage, filter it to look like an old aged photo, and return a modified CIImage.

Add the following method to ViewController:

func oldPhoto(img: CIImage, withAmount intensity: Float) -> CIImage {
  // 1
  let sepia = CIFilter(name:"CISepiaTone")
  sepia.setValue(img, forKey:kCIInputImageKey)
  sepia.setValue(intensity, forKey:"inputIntensity")
 
  // 2
  let random = CIFilter(name:"CIRandomGenerator")
 
  // 3
  let lighten = CIFilter(name:"CIColorControls")
  lighten.setValue(random.outputImage, forKey:kCIInputImageKey)
  lighten.setValue(1 - intensity, forKey:"inputBrightness")
  lighten.setValue(0, forKey:"inputSaturation")
 
  // 4
  let croppedImage = lighten.outputImage.imageByCroppingToRect(beginImage.extent())
 
  // 5
  let composite = CIFilter(name:"CIHardLightBlendMode")
  composite.setValue(sepia.outputImage, forKey:kCIInputImageKey)
  composite.setValue(croppedImage, forKey:kCIInputBackgroundImageKey)
 
  // 6
  let vignette = CIFilter(name:"CIVignette")
  vignette.setValue(composite.outputImage, forKey:kCIInputImageKey)
  vignette.setValue(intensity * 2, forKey:"inputIntensity")
  vignette.setValue(intensity * 30, forKey:"inputRadius")
 
  // 7
  return vignette.outputImage
}

Here’s what’s going on, section by section:

  1. Set up the sepia filter the same way you did in the simpler scenario. You’re passing in a Float value in the method to set the intensity of the sepia effect. This value will be provided by the slider.
  2. Set up a filter that creates a random noise pattern that looks like this:It doesn’t take any parameters. You’ll use this noise pattern to add texture to your final “old photo” look.
  3. Alter the output of the random noise generator. You want to change it to grayscale, and lighten it up a little bit so the effect is less dramatic. You’ll notice that the input image key is set to the outputImage property of the random filter. This is a convenient way to pass the output of one filter as the input of the next.
  4. imageByCroppingToRect() takes an output CIImage and crops it to the provided rect. In this case, you need to crop the output of the CIRandomGenerator filter because it tiles infinitely. If you don’t crop it at some point, you’ll get an error saying that the filters have ‘an infinite extent’. CIImages don’t actually contain image data, they describe a ‘recipe’ for creating it. It’s not until you call a method on the CIContext that the data is actually processed.
  5. Combine the output of the sepia filter with the output of the CIRandomGenerator filter. This filter performs the exact same operation as the ‘Hard Light’ setting does in a photoshop layer. Most (if not all) of the filter options in photoshop are achievable using Core Image.
  6. Run a vignette filter on this composited output that darkens the edges of the photo. You’re using the value from the slider to set the radius and intensity of this effect.
  7. Finally, return the output of the last filter.

That’s all for this filter chain. You should now have an idea of how complex these filter chains may become. By combining Core Image filters into these kinds of chains, you can achieve an endless variety of effects.

The next thing to do is implement this method in amountSliderValueChanged(). Change these two lines:

filter.setValue(sliderValue, forKey: "inputIntensity")
let outputImage = filter.outputImage

To this one line:

let outputImage = self.oldPhoto(beginImage, withAmount: sliderValue)

This just replaces the previous sepia effect with your new, more complex filter method. You pass in the slider value for the intensity and you use the beginImage, which you set in the viewDidLoad method as the input CIImage. Build and run now and you should get a more refined old photo effect, complete with sepia, a little noise, and some vignetting.

coreimage-aged

That noise could probably be more subtle, but I’ll leave that experiment to you, dear reader. Now you have the full power of Core Image at your disposal. Go crazy!

Where To Go From Here?

Here is the example project with all of the code from the above tutorial.

That about covers the basics of using Core Image filters. It’s a pretty handy technique, and you should be able to use it to apply some neat filters to images quite quickly!

Remember, the sample app prints out a list of all the available filters to the console when it starts up. Why not try playing around with some of those? You can check out the Core Image filter reference documentation for notes on all the filters.

If you have any questions or comments on this tutorial or Core Image in general, please join the forum discussion below!

Beginning Core Image in Swift is a post from: Ray Wenderlich

The post Beginning Core Image in Swift appeared first on Ray Wenderlich.


How To Make an App Like RunKeeper: Part 2

$
0
0

32_custom_callout

This is the second and final part of a tutorial that teaches you how to create an app like RunKeeper, complete with color-coded maps and badges!

In the first section of the tutorial, you created an app that:

  • Uses Core Location to track your route
  • Continually maps your path and reports your average pace as you run
  • Shows a map of your route when the run is complete; the map line is color-coded so that the slower portions are red and the faster portions are green.

That app is great for recording and displaying data, but to spark that motivation, sometimes you just need a little more of a nudge than a pretty map can provide.

So in this section, you’ll complete the demo MoonRunner app with a badge system that embodies the concept that fitness is a fun and progress-based achievement. Here’s how it works:

  • A list maps out checkpoints of increasing distance to motivate the user.
  • As the user runs, they can see a thumbnail of the next upcoming badge and the remaining distance to it.
  • The first time a user has a run reaching a certain checkpoint, the app awards a badge and notes that run’s average speed.
  • From there, silver and gold versions of the badge are awarded for reaching the checkpoint again at a proportionally faster speed.
  • The post-run map displays a dot at each checkpoint along the path, with a custom callout showing the badge name and image.

Getting Started

Your creativity is the limit when it comes to the theme your badges might take. But I just finished watching the new Cosmos television series, so I was inspired to make all these badges planets, moons, or other milestones in our Solar System. How cosmic!

42_thats_no_moon

You already downloaded the MoonRunner resource pack in part one and added it to your project. Notice that the pack also contains quite a few images and a file named badges.txt. Open badges.txt. Notice that it contains a large JSON array of badge objects. Each badge object contains:

  • A name
  • A piece of cool/interesting information about the badge
  • The distance in meters to achieve this badge
  • The filename of the corresponding image in the resource pack

The badges go all the way from 0 meters — hey, you have to start somewhere — up to the length of a full marathon. Of course, some people choose to go even further on ultra-marathons, and you can consider those ambitious runners as having entered interstellar space.

So the first task is to parse the JSON text into an array of objects. Select File\New\File and the iOS\Cocoa Touch\Objective-C class to create a class Badge that extends NSObject.

Then edit Badge.h to match the following:

#import <Foundation/Foundation.h>
 
@interface Badge : NSObject
 
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *imageName;
@property (strong, nonatomic) NSString *information;
@property float distance;
 
@end

Now you have your Badge object, and it’s time to parse the source JSON. Create a new class BadgeController extending NSObject, and edit the header as follows:

#import <Foundation/Foundation.h>
 
@interface BadgeController : NSObject
 
+ (BadgeController *)defaultController;
 
@end

This class will have a single instance, created once and accessed with defaultController. Open BadgeController.m. Replace the file contents with the following code:

#import "BadgeController.h"
#import "Badge.h"
 
@interface BadgeController ()
 
@property (strong, nonatomic) NSArray *badges;
 
@end
 
@implementation BadgeController
 
+ (BadgeController *)defaultController
{   
    static BadgeController *controller = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        controller = [[BadgeController alloc] init];
        controller.badges = [self badgeArray];
    });
 
    return controller;
}
 
+ (NSArray *)badgeArray
{
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"badges" ofType:@"txt"];
    NSString *jsonContent = [NSString stringWithContentsOfFile:filePath usedEncoding:nil error:nil];
    NSData *data = [jsonContent dataUsingEncoding:NSUTF8StringEncoding];
    NSArray *badgeDicts = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
 
    NSMutableArray *badgeObjects = [NSMutableArray array];
 
    for (NSDictionary *badgeDict in badgeDicts) {
        [badgeObjects addObject:[self badgeForDictionary:badgeDict]];
    }
 
    return badgeObjects;
}
 
+ (Badge *)badgeForDictionary:(NSDictionary *)dictionary
{
    Badge *badge = [Badge new];
    badge.name = [dictionary objectForKey:@"name"];
    badge.information = [dictionary objectForKey:@"information"];
    badge.imageName = [dictionary objectForKey:@"imageName"];
    badge.distance = [[dictionary objectForKey:@"distance"] floatValue];
    return badge;
}
 
@end

There are three methods here that all work together:

  1. defaultController is publicly accessible, delivers a single instance of the controller, and makes sure the parsing operation happens only once.
  2. badgeArray extracts an array from the text file and creates an object from each element of the array.
  3. badgeForDictionary: does the actual mapping of JSON keys to the Badge object.

And with that, you’re all set to retrieve the badge data. Time to get that data incorporated into your app!

The Badge Storyboards

What’s the good of winning all these badges if you can’t keep them in a nice glass case and marvel at them every once in a while? Or maybe you’re a little more modest and you just say you want to “keep track” of them. :]

In any case, it’s time to add some UI to your storyboards to view badges. Open Main.storyboard. Drag a new table view controller onto the storyboard and control-drag from the Badges button on the Home Screen to create a push segue:

24_segue_badgetable

Next, select the table view inside the table view controller you just added and open the size inspector. Increase the Row Height of the table to 80 points. Then select the prototype cell in the table view and open the attributes inspector. Set the Style to Custom and Identifier to BadgeCell.

Inside the cell, set the customary MoonRunner black background and add a large UIImageView on the left side. Add two smaller UIImageViews with the spaceship-gold and spaceship-silver assets just to its right. Then add two UILabels. Use the screenshot below to help you design the cell:

25_badge_cell

Each badge will occupy one of these cells, with its image on the left and a description of when you earned the badge, or what you need to do to earn it. The two small UIImageViews are for the silver and gold spaceship icons, in case the user earned those levels.

Next, drag in a new view controller (normal one, not table view controller this time) onto the storyboard. Then control-drag from the table view cell in the table view controller you just added to this new view controller, and select push selection segue.

Then style this new screen to look like this:

26_badge_detail_view

On this detail screen, you’ll see:

  • A large UIImageView to show off the badge image — some of these photos from space are stunning
  • A small UIButton on top of the UIImageView, using the info image as a background.
  • A UILabel for the badge Name
  • A UILabel for the badge Distance
  • A UILabel for the date the badge was Earned
  • A UILabel for the Best average pace for this distance
  • A UILabel for the date the user earned the Silver version of the badge (or the pace needed to earn it, if they have yet to do so)
  • The same for the Gold version of the badge
  • Two small UIImageViews with the spaceship-gold and spaceship-silver assets.

The badge information is important because it gives the users a little context, placing their accomplishment in a sort of story or map. The badge images, however small, serve as an extra little reward.

Earning The Badge

You’ve already created the Badge object, and now you need an object to store when a badge was earned. This object will associate a Badge with the various Run objects, if any, where the user achieved versions of this badge.

Click File\New\File. Select iOS\Cocoa Touch\Objective-C class. Call the class BadgeEarnStatus, extending NSObject, and save the file. Then open BadgeEarnStatus.h and replace its contents with the following:

#import <Foundation/Foundation.h>
 
@class Badge;
@class Run;
 
@interface BadgeEarnStatus : NSObject
 
@property (strong, nonatomic) Badge *badge;
@property (strong, nonatomic) Run *earnRun;
@property (strong, nonatomic) Run *silverRun;
@property (strong, nonatomic) Run *goldRun;
@property (strong, nonatomic) Run *bestRun;
 
@end

Then open BadgeEarnStatus.m and add the following imports at the top of the file:

#import "Badge.h"
#import "Run.h"

Now that you have an easy way to associate a Badge with a Run, it’s time to build the logic to make those associations. Open BadgeController.h and add the following constants at the top of the file:

extern float const silverMultiplier;
extern float const goldMultiplier;

Then add the following instance method to the interface:

- (NSArray *)earnStatusesForRuns:(NSArray *)runArray;

The constants silverMultiplier and goldMultiplier will be available to other classes. They are the factors by which a user has to speed up to earn those versions of the badge.

Open BadgeController.m and add the following imports and constant definitions at the top of the file:

#import "BadgeEarnStatus.h"
#import "Run.h"
 
float const silverMultiplier = 1.05; // 5% speed increase
float const goldMultiplier = 1.10; // 10% speed increase

Then, add the following method to the implementation:

- (NSArray *)earnStatusesForRuns:(NSArray *)runs {
    NSMutableArray *earnStatuses = [NSMutableArray array];
 
    for (Badge *badge in self.badges) {
 
        BadgeEarnStatus *earnStatus = [BadgeEarnStatus new];
        earnStatus.badge = badge;
 
        for (Run *run in runs) {
 
            if (run.distance.floatValue > badge.distance) {
 
                // this is when the badge was first earned
                if (!earnStatus.earnRun) {
                    earnStatus.earnRun = run;
                }
 
                double earnRunSpeed = earnStatus.earnRun.distance.doubleValue / earnStatus.earnRun.duration.doubleValue;
                double runSpeed = run.distance.doubleValue / run.duration.doubleValue;
 
                // does it deserve silver?
                if (!earnStatus.silverRun
                    && runSpeed > earnRunSpeed * silverMultiplier) {
 
                    earnStatus.silverRun = run;
                }
 
                // does it deserve gold?
                if (!earnStatus.goldRun
                    && runSpeed > earnRunSpeed * goldMultiplier) {
 
                    earnStatus.goldRun = run;
                }
 
                // is it the best for this distance?
                if (!earnStatus.bestRun) {
                    earnStatus.bestRun = run;
 
                } else {
                    double bestRunSpeed = earnStatus.bestRun.distance.doubleValue / earnStatus.bestRun.duration.doubleValue;
 
                    if (runSpeed > bestRunSpeed) {
                        earnStatus.bestRun = run;
                    }
                }
            }
        }
 
        [earnStatuses addObject:earnStatus];
    }
 
    return earnStatuses;
}

This method compares all the user’s runs to the distance requirements for each badge, making the associations and returning all the BadgeEarnStatus objects in an array.

To do this, the first time a user reaches the badge it finds a earnRunSpeed, which becomes a reference to see if the user improved enough to qualify for the silver or gold versions.

Think of it this way – you’ll still have a chance at earning the gold version of the Mars badge, even if your friend is much faster every time. As long as you’re both making progress, you both win.

35_making_progress

Note: You can see this method uses the overall average speed of a run. For a little extra challenge, try working with more intensive math to also use portions of a run to calculate speeds. For example, your fastest mile could have been in the middle of a two-mile run with a slow beginning and end.

Displaying the Badges

Now it’s time to bring all this badge logic and UI together for the user. You’re going to create two view controllers and one custom table cell in order to link the storyboards with the badge data.

First, create a new class called BadgeCell extending UITableViewCell. Open BadgeCell.h and make it look like this:

#import <UIKit/UIKit.h>
 
@interface BadgeCell : UITableViewCell
 
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
@property (nonatomic, weak) IBOutlet UILabel *descLabel;
@property (nonatomic, weak) IBOutlet UIImageView *badgeImageView;
@property (nonatomic, weak) IBOutlet UIImageView *silverImageView;
@property (nonatomic, weak) IBOutlet UIImageView *goldImageView;
 
@end

Now you have a custom cell to use in the table view controller for badges you added earlier.

Next, create a class named BadgesTableViewController extending UITableViewController. Open BadgesTableViewController.h and make it look like this:

#import <UIKit/UIKit.h>
 
@interface BadgesTableViewController : UITableViewController
 
@property (strong, nonatomic) NSArray *earnStatusArray;
 
@end

The earnStatusArray will be the result of calling earnStatusesForRuns: in the BadgeController — the method you added earlier to calculate badge statuses.

Open BadgesTableViewController.m and add the following imports to the top of the file:

#import "BadgeEarnStatus.h"
#import "BadgeCell.h"
#import "Badge.h"
#import "MathController.h"
#import "Run.h"

Then add the following properties to the class extension category:

@interface BadgesTableViewController ()
 
@property (strong, nonatomic) UIColor *redColor;
@property (strong, nonatomic) UIColor *greenColor;
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (assign, nonatomic) CGAffineTransform transform;
 
@end

These are a few properties that will be used throughout the table view controller. The colors will be used to color each cell according to whether or not the badge has been earned, for example.

Find viewDidLoad in the implementation and make it look like this:

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    self.redColor = [UIColor colorWithRed:1.0f green:20/255.0 blue:44/255.0 alpha:1.0f];
    self.greenColor = [UIColor colorWithRed:0.0f green:146/255.0 blue:78/255.0 alpha:1.0f];
    self.dateFormatter = [[NSDateFormatter alloc] init];
    [self.dateFormatter setDateStyle:NSDateFormatterMediumStyle];
    self.transform = CGAffineTransformMakeRotation(M_PI/8);
}

This sets up the properties you just added. The properties are essentially caches so that each time a new cell is created you don’t need to recreate the required properties over and over. Date formatters are especially expensive to create and therefore a good idea to cache if you can.

Next, remove the implementations of tableView:numberOfRowsInSection: and numberOfSectionsInTableView:. Then add the following methods:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.earnStatusArray.count;
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    BadgeCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BadgeCell" forIndexPath:indexPath];
    BadgeEarnStatus *earnStatus = [self.earnStatusArray objectAtIndex:indexPath.row];
 
    cell.silverImageView.hidden = !earnStatus.silverRun;
    cell.goldImageView.hidden = !earnStatus.goldRun;
 
    if (earnStatus.earnRun) {
        cell.nameLabel.textColor = self.greenColor;
        cell.nameLabel.text = earnStatus.badge.name;
        cell.descLabel.textColor = self.greenColor;
        cell.descLabel.text = [NSString stringWithFormat:@"Earned: %@", [self.dateFormatter stringFromDate:earnStatus.earnRun.timestamp]];
        cell.badgeImageView.image = [UIImage imageNamed:earnStatus.badge.imageName];
        cell.silverImageView.transform = self.transform;
        cell.goldImageView.transform = self.transform;
        cell.userInteractionEnabled = YES;
    } else {
        cell.nameLabel.textColor = self.redColor;
        cell.nameLabel.text = @"?????";
        cell.descLabel.textColor = self.redColor;
        cell.descLabel.text = [NSString stringWithFormat:@"Run %@ to Earn", [MathController stringifyDistance:earnStatus.badge.distance]];
        cell.badgeImageView.image = [UIImage imageNamed:@"question_badge.png"];
        cell.userInteractionEnabled = NO;
    }
 
    return cell;
}

These methods tell the table view how many rows to show (the number of badges) and how to set up each cell. As you can see, every cell depends on if the user earned the badge, among other things. Also, the cell can only be selected if the badge has been earned, through the use of userInteractionEnabled.

Now you need to make the badges table view controller have some data to work with. Open HomeViewController.m and add these imports at the top of the file:

#import "BadgesTableViewController.h"
#import "BadgeController.h"

Add this property to the class extension category:

@property (strong, nonatomic) NSArray *runArray;

Now add the following method:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
 
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription
                                   entityForName:@"Run" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
 
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:NO];
    [fetchRequest setSortDescriptors:@[sortDescriptor]];
 
    self.runArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
}

This will have the effect of refreshing the run array every time the view controller appears. It does this using a Core Data fetch to fetch all the runs sorted by timestamp.

Finally, add to prepareForSegue:sender: so it looks like this:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    UIViewController *nextController = [segue destinationViewController];
    if ([nextController isKindOfClass:[NewRunViewController class]]) {
        ((NewRunViewController *) nextController).managedObjectContext = self.managedObjectContext;
    } else if ([nextController isKindOfClass:[BadgesTableViewController class]]) {
        ((BadgesTableViewController *) nextController).earnStatusArray = [[BadgeController defaultController] earnStatusesForRuns:self.runArray];
    }
}

Here, when the badges table view controller is being pushed onto the navigation stack, the earn status of all badges is calculated and passed to the badge table view controller.

Now it’s time to connect all your outlets in the storyboard. Open Main.storyboard and do the following:

  • Set the classes of BadgeCell and BadgesTableViewController
  • .

  • Connect the outlets of BadgeCell: nameLabel, descLabel, badgeImageView, silverImageView, and goldImageView

.

Build &run and check out your new badges! You should see something like this:

33_badge_list

You will only have the ‘Earth’ badge currently – but it’s a start! Now to grab some more badges!

What Does A Runner Have To Do To Get A Gold Medal Around Here?

The last view controller for MoonRunner is the one that shows the details of a badge. Create a new class called BadgeDetailsViewController, extending from UIViewController. Open BadgeDetailsViewController.h and replace its contents with the following:

#import <UIKit/UIKit.h>
 
@class BadgeEarnStatus;
 
@interface BadgeDetailsViewController : UIViewController
 
@property (strong, nonatomic) BadgeEarnStatus *earnStatus;
 
@end

Then open BadgesTableViewController.m. Add the following import at the top of the file:

#import "BadgeDetailsViewController.h"

And add the following method:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue destinationViewController] isKindOfClass:[BadgeDetailsViewController class]]) {
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        BadgeEarnStatus *earnStatus = [self.earnStatusArray objectAtIndex:indexPath.row];
        [(BadgeDetailsViewController *)[segue destinationViewController] setEarnStatus:earnStatus];
    }
}

This sets up the segue for when a cell is tapped. It hands over the relevant BadgeEarnStatus instance for the detail view controller to display.

Open BadgeDetailsViewController.m, and add the following imports at the top of the file:

#import "BadgeEarnStatus.h"
#import "Badge.h"
#import "MathController.h"
#import "Run.h"
#import "BadgeController.h"

Then add the following properties to the class extension category:

@interface BadgeDetailsViewController ()
 
@property (nonatomic, weak) IBOutlet UIImageView *badgeImageView;
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
@property (nonatomic, weak) IBOutlet UILabel *distanceLabel;
@property (nonatomic, weak) IBOutlet UILabel *earnedLabel;
@property (nonatomic, weak) IBOutlet UILabel *silverLabel;
@property (nonatomic, weak) IBOutlet UILabel *goldLabel;
@property (nonatomic, weak) IBOutlet UILabel *bestLabel;
@property (nonatomic, weak) IBOutlet UIImageView *silverImageView;
@property (nonatomic, weak) IBOutlet UIImageView *goldImageView;
 
@end

These are all IBOutlets to the UI.

Now find viewDidLoad and make it look like this:

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
 
    CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI/8);
 
    self.nameLabel.text = self.earnStatus.badge.name;
    self.distanceLabel.text = [MathController stringifyDistance:self.earnStatus.badge.distance];
    self.badgeImageView.image = [UIImage imageNamed:self.earnStatus.badge.imageName];
    self.earnedLabel.text = [NSString stringWithFormat:@"Reached on %@" , [formatter stringFromDate:self.earnStatus.earnRun.timestamp]];
 
    if (self.earnStatus.silverRun) {
        self.silverImageView.transform = transform;
        self.silverImageView.hidden = NO;
        self.silverLabel.text = [NSString stringWithFormat:@"Earned on %@" , [formatter stringFromDate:self.earnStatus.silverRun.timestamp]];
 
    } else {
        self.silverImageView.hidden = YES;
        self.silverLabel.text = [NSString stringWithFormat:@"Pace < %@ for silver!", [MathController stringifyAvgPaceFromDist:(self.earnStatus.earnRun.distance.floatValue * silverMultiplier) overTime:self.earnStatus.earnRun.duration.intValue]];
    }
 
    if (self.earnStatus.goldRun) {
        self.goldImageView.transform = transform;
        self.goldImageView.hidden = NO;
        self.goldLabel.text = [NSString stringWithFormat:@"Earned on %@" , [formatter stringFromDate:self.earnStatus.goldRun.timestamp]];
 
    } else {
        self.goldImageView.hidden = YES;
        self.goldLabel.text = [NSString stringWithFormat:@"Pace < %@ for gold!", [MathController stringifyAvgPaceFromDist:(self.earnStatus.earnRun.distance.floatValue * goldMultiplier) overTime:self.earnStatus.earnRun.duration.intValue]];
    }
 
    self.bestLabel.text = [NSString stringWithFormat:@"Best: %@, %@", [MathController stringifyAvgPaceFromDist:self.earnStatus.bestRun.distance.floatValue overTime:self.earnStatus.bestRun.duration.intValue], [formatter stringFromDate:self.earnStatus.bestRun.timestamp]];
}

This code sets up the badge image and puts all the data about the badge earning into the labels.

The most interesting parts are the prompts that tell the user how much they need to speed up to earn the coveted silver and gold badges. I’ve found these prompts to be very motivating, as the pace is always an improvement that requires effort but is obviously possible.

Finally, add the following method:

- (IBAction)infoButtonPressed:(UIButton *)sender
{
    UIAlertView *alertView = [[UIAlertView alloc]
                              initWithTitle:self.earnStatus.badge.name
                              message:self.earnStatus.badge.information
                              delegate:nil
                              cancelButtonTitle:@"OK"
                              otherButtonTitles:nil];
    [alertView show];
}

This will be invoked when the info button is pressed. It shows a pop-up with the badge’s information.

Great! Now the code side of the badge UI is all set. Open Main.storyboard and make the following connections:

  • Set the BadgeDetailsViewController class.
  • Connect the outlets of BadgeDetailsViewController: badgeImageView, bestLabel, distanceLabel, earnedLabel, goldImageView, goldLabel, nameLabel, silverImageLabel, and silverLabel.
  • The received action infoButtonPressed: to BadgeDetailsView.

Now build & run and check out your new badge details!

34_badge_details

Carrot Motivation

Along with the new portion of the app devoted to badges, you need to go back through the UI of the existing app and update it to incorporate the badges!

Open Main.storyboard and find the ‘New Run’ view controller. Add a UIImageView and a UILabel to its view, somewhere above the Stop button. It should now look like this:

39_next_badge

These will be used as a “carrot-on-a-stick” type motivator for the user as they run, giving them a sneak peak at the next badge and how much farther away it is.

Before you can hook up the UI, you need to add a couple methods to BadgeController to determine which badge is best for a certain distance, and which one is coming up next.

Open BadgeController.h and add the following method declarations to the interface:

- (Badge *)bestBadgeForDistance:(float)distance;
- (Badge *)nextBadgeForDistance:(float)distance;

Also add this line above the interface, just below the imports:

@class Badge;

Now open BadgeController.m and implement those methods like this:

- (Badge *)bestBadgeForDistance:(float)distance {
    Badge *bestBadge = self.badges.firstObject;
    for (Badge *badge in self.badges) {
        if (distance < badge.distance) {
            break;
        }
        bestBadge = badge;
    }
    return bestBadge;
}
 
- (Badge *)nextBadgeForDistance:(float)distance {
    Badge *nextBadge;
    for (Badge *badge in self.badges) {
        nextBadge = badge;
        if (distance < badge.distance) {
            break;
        }
    }
    return nextBadge;
}

These are fairly straightforward — they each take an input, distance in meters, and return either:

  • bestBadgeForDistance: The badge that was last won.
  • nextBadgeForDistance: The badge that is next to be won.

Now open NewRunViewController.m and add the following imports at the top of the file:

#import <AudioToolbox/AudioToolbox.h>
#import "BadgeController.h"
#import "Badge.h"

The badge-related imports are obviously needed, and the AudioToolbox import is so that a sound can be played every time you earn a new badge.

Add these three properties to the class extension category:

@property (nonatomic, strong) Badge *upcomingBadge;
@property (nonatomic, weak) IBOutlet UILabel *nextBadgeLabel;
@property (nonatomic, weak) IBOutlet UIImageView *nextBadgeImageView;

Then find viewWillAppear: and add the following code at the end of the method:

self.nextBadgeLabel.hidden = YES;
self.nextBadgeImageView.hidden = YES;

Just like the other views, the badge label and badge image need to start out hidden.

Then find startPressed: and add the following code at the end of the method:

self.nextBadgeImageView.hidden = NO;
self.nextBadgeLabel.hidden = NO;

This ensures that the badge label and badge image show up when the run starts.

Now find eachSecond and add the following code at the end of the method:

self.nextBadgeLabel.text = [NSString stringWithFormat:@"%@ until %@!", [MathController stringifyDistance:(self.upcomingBadge.distance - self.distance)], self.upcomingBadge.name];
[self checkNextBadge];

This makes sure nextBadgeLabel is always up-to-date as the run progresses.

Then, add this new method:

- (void)checkNextBadge
{
    Badge *nextBadge = [[BadgeController defaultController] nextBadgeForDistance:self.distance];
 
    if (self.upcomingBadge
        && ![nextBadge.name isEqualToString:self.upcomingBadge.name]) {
 
        [self playSuccessSound];
    }
 
    self.upcomingBadge = nextBadge;
    self.nextBadgeImageView.image = [UIImage imageNamed:nextBadge.imageName];
}

This method obtains the next badge using the method you added previously. It checks to see if this badge is new, by comparing the name to the existing upcoming badge store in the upcomingBadge property. If the badge is different, then a success sound is played because that means a new badge had been earned!

You’ll notice that you haven’t implemented playSuccessSound yet. Add the following method:

- (void)playSuccessSound
{
    NSString *path = [NSString stringWithFormat:@"%@%@", [[NSBundle mainBundle] resourcePath], @"/success.wav"];
    SystemSoundID soundID;
    NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];
    AudioServicesCreateSystemSoundID((CFURLRef)CFBridgingRetain(filePath), &soundID);
    AudioServicesPlaySystemSound(soundID);
 
    //also vibrate
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}

This plays the success sound, but it also vibrates the phone using the system vibrate sound ID. It also helps to vibrate the phone, in case the user is running in a noisy location, like next to a busy road. Or maybe they’ve got their music on and can’t hear the sound anyway!

Open Main.storyboard and find the ‘New Run’ view controller. Connect the IBOutlets for nextBadgeLabel and nextBadgeImageView. Then build & run to watch the label and image update as you run! Stick around for the sound when you pass a new badge!

36_breadcrumbs

Everything Is Better When It Has a “Space Mode”

After a run has finished, it would be nice to provide the user with the ability to see the last badge that they earned during that run. Let’s add that!

Open Main.storyboard and find the ‘Run Details’ view controller. Add a UIImageView with the same frame as the existing MKMapView (directly on top of it). Then add a UIButton with the info image on it, and a UISwitch with an explanatory UILabel above it. The UI should look like this:

30_space_mode_board

Hide the image view you just added by selecting Hidden from the attributes inspector.

This gives your run details a little more personality, with the capability to switch on a “Space Mode” that shows the freshly-earned badge and its information after a run. There’s no feeling quite like finishing a run and finding out you went all the way to Jupiter. :]

Open DetailViewController.m and add the following imports to the top of the file:

#import "Badge.h"
#import "BadgeController.h"

Next, add these two properties to the class extension category:

@property (nonatomic, weak) IBOutlet UIImageView *badgeImageView;
@property (nonatomic, weak) IBOutlet UIButton *infoButton;

Then add the following code to the end of configureView:

Badge *badge = [[BadgeController defaultController] bestBadgeForDistance:self.run.distance.floatValue];
self.badgeImageView.image = [UIImage imageNamed:badge.imageName];

This sets up the badge image view with the image for the badge that was last earned. It does this by fetching the badge that was last earned using the method that you added previously.

Now add the following method:

- (IBAction)displayModeToggled:(UISwitch *)sender
{
    self.badgeImageView.hidden = !sender.isOn;
    self.infoButton.hidden = !sender.isOn;
    self.mapView.hidden = sender.isOn;
}

This will be fired when the switch is toggled. It swaps out the map for the image when the switch is on, i.e. when they are in “Space Mode”!

And finally, add the following method:

- (IBAction)infoButtonPressed
{
    Badge *badge = [[BadgeController defaultController] bestBadgeForDistance:self.run.distance.floatValue];
 
    UIAlertView *alertView = [[UIAlertView alloc]
                              initWithTitle:badge.name
                              message:badge.information
                              delegate:nil
                              cancelButtonTitle:@"OK"
                              otherButtonTitles:nil];
    [alertView show];
}

This will be fired when the info button is pressed. It shows an alert with the badge information.

Now open Main.storyboard and find the ‘Run Details’ view controller. Connect badgeImageView, infoButton, displayModeToggled:, and infoButtonPressed to the views you recently added. Then build & run and bask in the glory of the badge you earn after that long, hard run!

31_badge_achieved

Mapping the Solar System In Your Town

The post-run map already helps you remember your route, and even identify specific areas where your speed was lower. Another helpful feature that would be nice to add is to note exactly when you pass each badge checkpoint, so you can divide up your run.

Annotations are how map views can display point data like this. These are the moving parts you need:

  • A class adopting MKAnnotation handles the data side of the annotation. Your class provides a coordinate to allow the map to know where to put the annotation.
  • A class extending MKAnnotationView arranges the incoming data from an MKAnnotation into its visual form.

So you’ll begin by arranging the badge data into an array of objects conforming to MKAnnotation. Then you’ll use the MKMapViewDelegate method mapView:viewForAnnotation: to translate that data into MKAnnotationViews.

Create a new class named BadgeAnnotation which extends MKPointAnnotation. Then open BadgeAnnotation.h and replace its contents with the following code:

#import <MapKit/MapKit.h>
 
@interface BadgeAnnotation : MKPointAnnotation
 
@property (strong, nonatomic) NSString *imageName;
 
@end

Then open BadgeController.h and add this method declaration to the interface:

- (NSArray *)annotationsForRun:(Run *)run;

Add this line under the imports in the same file:

@class Run;

Next, open BadgeController.m and add the following imports to the top of the file:

#import <MapKit/MapKit.h>
#import "Location.h"
#import "MathController.h"
#import "BadgeAnnotation.h"

And then add the following method to the implementation:

- (NSArray *)annotationsForRun:(Run *)run
{
    NSMutableArray *annotations = [NSMutableArray array];
 
    int locationIndex = 1;
    float distance = 0;
 
    for (Badge *badge in self.badges) {
        if (badge.distance > run.distance.floatValue) {
            break;
        }
 
        while (locationIndex < run.locations.count) {
 
            Location *firstLoc = [run.locations objectAtIndex:(locationIndex-1)];
            Location *secondLoc = [run.locations objectAtIndex:locationIndex];
 
            CLLocation *firstLocCL = [[CLLocation alloc] initWithLatitude:firstLoc.latitude.doubleValue longitude:firstLoc.longitude.doubleValue];
            CLLocation *secondLocCL = [[CLLocation alloc] initWithLatitude:secondLoc.latitude.doubleValue longitude:secondLoc.longitude.doubleValue];
 
            distance += [secondLocCL distanceFromLocation:firstLocCL];
            locationIndex++;
 
            if (distance >= badge.distance) {
                BadgeAnnotation *annotation = [[BadgeAnnotation alloc] init];
                annotation.coordinate = secondLocCL.coordinate;
                annotation.title = badge.name;
                annotation.subtitle = [MathController stringifyDistance:badge.distance];
                annotation.imageName = badge.imageName;
                [annotations addObject:annotation];
                break;
            }
        }
    }
 
    return annotations;
}

This method loops over all the location points in the run and keeps a cumulative distance for the run. When the cumulative distance passes the next badge’s threshold, a BadgeAnnotation is created. This annotation provides the coordinates of where the badge was earned, the badge’s name, the distance through the run and the badge image name.

Now you have the data ready!

Open DetailViewController.m. Add this import to the top of the file:

#import "BadgeAnnotation.h"

Then, add the following method:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation
{
    BadgeAnnotation *badgeAnnotation = (BadgeAnnotation *)annotation;
 
    MKAnnotationView *annView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"checkpoint"];
    if (!annView) {
        annView=[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"checkpoint"];
        annView.image = [UIImage imageNamed:@"mapPin"];
        annView.canShowCallout = YES;
    }
 
    UIImageView *badgeImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 75, 50)];
    badgeImageView.image = [UIImage imageNamed:badgeAnnotation.imageName];
    badgeImageView.contentMode = UIViewContentModeScaleAspectFit;
    annView.leftCalloutAccessoryView = badgeImageView;
 
    return annView;
}

This is part of the MKMapViewDelegate protocol. It is called every time the map wants a view for the given annotation. It’s your job to return a view and the map view will then handle the logic of putting it in the right place on the map.

In this method, you take an incoming annotation and tell the map how to render it. Notice that the title and subtitle properties don’t make an appearance — that rendering happens automatically inside Map Kit.

Then find loadMap and add the following line of code just underneath the call do addOverlays::

[self.mapView addAnnotations:[[BadgeController defaultController] annotationsForRun:self.run]];

With that, you tell the map view to add all the annotations.

Now you can look at your map after a run, and see all the dots that mean you passed a checkpoint! Build & run the app, start and finish a run, and click Save. The map will now have annotations for each badge earned. Click on one, and you can see its name, picture and distance. Neat!

32_custom_callout

And that’s it! The app is complete! Congratulations on finishing the app.

Where to go From Here

Over the course of this two-part tutorial, you built an app that:

  • Measures and tracks your runs using Core Location
  • Displays real-time data, like the run’s average pace, along with an active map
  • Maps out a run with a color-coded polyline and custom annotations at each checkpoint.
  • Awards badges for personal progress in distance and speed.

Ideas to take it to the next level:

  • Add a table for a user’s past runs.
  • Try using the speeds for segments of a run to earn badges.
    • For example, if you run your fastest 5 km in the middle of a 10 km run, how do you count that towards a silver or gold 5 km badge?
    • Find the average pace between each checkpoint and display it on the MKAnnotationView callout
    • Add badges for lifetime achievements, for instance, running a total of 100 km in a week, 1000 km all-time, etc.
    • Sync a user’s run data with a server.

If you’d like to see how it looks when it’s done, download the full project code here.

Thank you for reading! If you have questions, comments or want to share your successes, please post in the comments below! Happy running through the Solar System! :]

How To Make an App Like RunKeeper: Part 2 is a post from: Ray Wenderlich

The post How To Make an App Like RunKeeper: Part 2 appeared first on Ray Wenderlich.

CloudKit: The raywenderlich.com Podcast Episode 9

$
0
0
Episode 9: CloudKit

Episode 9: CloudKit

In this episode, we talk about CloudKit, Apple’s back-end-as-a-service (BaaS) new in iOS 8, along with special guest Mike Katz.

We also talk about the recent discussions about it being difficult to survive as an indie iOS developer.

[Subscribe in iTunes] [RSS Feed]

Here’s what is covered in this episode:

  • News: Surviving as an indie iOS developer
  • Tech Talk: Cloud Kit

Links and References

Our Sponsor

  • Appacitive: Ready to use backend APIs that help you reduce your total development time, giving you more time to innovate on your app.

News

Tech Talk: Cloud Kit

Contact Us

Where To Go From Here?

We hope you enjoyed this podcast! We have an episode each month, so be sure to subscribe in iTunes to get access as soon as it comes out.

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

CloudKit: The raywenderlich.com Podcast Episode 9 is a post from: Ray Wenderlich

The post CloudKit: The raywenderlich.com Podcast Episode 9 appeared first on Ray Wenderlich.

raywenderlich.com at Pragma Conference 2014

$
0
0
Authors from raywenderlich.com at Pragma Conference 2014!

Authors from raywenderlich.com at Pragma Conference 2014!

This is just a quick note to let you know that we are one of the proud sponsors of Pragma Conference 2014, this October in Milan, Italy.

Two authors from this site will be speaking there, and here’s what they’ll be speaking on:

Cesare Rocchi

Open Source Backends

There are many backend providers. Some are open source and they are actually not that complex to set up and get acquainted with.

In this presentation Cesare will provide an overview of the main open source backends. For each we will walk through the technology they are based on, their setup process, the features they support and the SDKs they include. The goal is to get to know better the twinkling ecosystem of open source backends that is constantly growing everyday.

Getting Started with BaasBox

BaasBox is an open source back end that allows you quickly building the talk-to-the-server part of your application. In this workshop you will learn how to use BaasBox’s iOS SDK and its features.

Marin Todorov

Sprite Kit Workshop

Marin Todorov held a Sprite Kit 101 presentation at the first Pragma Mark conference in 2013; that was less than a month after Sprite Kit was officially released! It was a mix of introduction to Sprite Kit and a live demo of building an Angry Birds clone in 20 minutes. The audience enjoyed this session so much that this year Pragma Mark has invited Marin Todorov for a half a day workshop on Sprite Kit and Swift!

The workshop is guaranteed to be lots of fun, there will be challenges for the ninjas in the audience, and of course Sprite Kit swag to win. If you don’t believe it – just ask people who were there last year :]

Where To Go From Here?

If these talks sound interesting to you, check out the Pragma Conference website. The organizers were kind enough to set up a generous 20% off discount for raywenderlich.com readers – just enter the coupon code PRAGMA-RAY20. Be sure to say hi to Cesare and Marin if you go!

Also stay tuned for our official raywenderlich.com conference, RWDevCon, which comes next February! :]

raywenderlich.com at Pragma Conference 2014 is a post from: Ray Wenderlich

The post raywenderlich.com at Pragma Conference 2014 appeared first on Ray Wenderlich.

Video Tutorial: Introduction to Swift Part 9: Enums

$
0
0

Challenge

Your challenge is to make an enum with the three Microsoft CEOs: Bill Gates, Steve Ballmer, and Satya Nadella. Make Satya Nadella the default, and add a description() method. Design your enum so that the following code works:

let currentCEO = MicrosoftCEOs()
println(currentCEO.description())
 
let oFirstCEO = MicrosoftCEOs.fromRaw(1)
if let firstCEO = oFirstCEO {
  println(firstCEO.description())
} else {
  println("No such value")
}

Download demo

Download challenge

Video Tutorial: Introduction to Swift Part 9: Enums is a post from: Ray Wenderlich

The post Video Tutorial: Introduction to Swift Part 9: Enums appeared first on Ray Wenderlich.

Video Tutorial: Introduction to Unity Part 3: Using the Scene and Game Views

How to Make Game Music for Beginners

$
0
0
Learn how to create your own game music!

Learn how to create your own game music!

So you’ve built a great game with awesome graphics, rock-solid gameplay, and a creative storyline. What more could you ask for?

Even with all that awesomeness, you’ll find the game lacking without some great in-game music.

Hiring a musician is the best bet of course, but if you don’t have the funds for that, that’s OK too. You can easily make your own game music, using some amazing iPad apps!

One such app I really like and personally use is Korg Gadget for iPad. It collects 15 mobile synthesizers and drum machines into a single app and wraps them all with an easy-to-use sequencer.

The only skills you need to compose music in this app are tapping and dragging. You can easily export a finished song from Korg Gadget and add it to your app.

In this tutorial I’ll take you through creating a simple track in Korg Gadget using multiple instruments, exporting the file, and finally including the finished song in your app.

Ready to discover the inner musician you never knew existed? Read on! :]

Getting Started

Download Korg Gadget on the App Store. Korg Gadget isn’t cheap; the current U.S. price on the App Store is $38.99, but occasionally you can find it on sale.

Open the Korg Gadet app on your iPad and tap the document icon in the upper left corner to create a new project, as shown below:

document-icon

Tap New, enter RW Game Track as the name, and tap OK.

The app then presents you with a list of sequencers, or Gadgets as they are called in the app. Choose the London drum machine as shown below:

1-Gadet-picker

The app then takes you to the Mixer view where you will build your song. By default, the top half of the screen displays list of tracks, like so:

Default View

Tap the Track 1 to enter Edit view:

002_EditView

At the top left of the screen you’ll see a Draw icon and a Select icon. Tapping these icons toggles you between “drawing” mode and “selecting” mode.

Tap any of the eight blue pads at the bottom of the screen and you’ll hear the London synthesizer drum sounds in real time. Note that the row names in your grid correspond to the names of each drum “voice”. Alternatively, you can tap on the row headings to hear the drum voices.

003_Pads

Tap the Drum Kit display on the left side of the console to open a popover showing the available drum sets. Scroll down and select 042 Lockeroom from the list.

004_DrumKit

To add a note to your song, simply tap the screen where you’d like to add the note. For your first attempt, recreate the pattern shown in the image below:

happy-pattern

If you’ve placed a note in the wrong spot, just tap the note again to remove it.

Press the Play icon in the bottom toolbar to play your song; each note is played as the yellow cursor scrolls by.

By default the green loop icon is selected, so the song repeats until you tap the pause icon. If you turn off loop mode, the song stops when it reaches the end of the track.

gadget-play-loop

If you want to clear your notes from the screen, press Hold to erase to wipe the notes as the cursor scrolls by.

I uploaded my example to SoundCloud so you can compare your results to mine:

Next, save your work in progress. To do this, tap Back to return to the Mixer View, tap the Document icon, then tap Save.

The basic drum groove is in place; next you’ll add a low drone sound to your track a la Blade Runner or Miami Vice.

Adding a Simple Drone

At this point you should be back in the Mixer View. Tap the circled + in the second grey box to create a new gadget, as shown below:

005_NewGadget

Choose the Chicago Gadget from the list, then tap the Chicago icon to enter Edit view.

You’ll note that this sequencer has fewer pads than the London synthesizer (7 versus the London synthesizer’s 8). The pads on this sequencer control the pitch of the note being played instead of the instrument voice. The black dots on the first, fourth, and seventh pads indicate an octave change.

006_Pads

Note: An octave normally spans eight whole notes in Western music — you probably know them as Do, Re, Mi, Fa, So, La, Ti and Do. The Chicago gadget provides a subset of those notes — the first, third and fifth notes, or Do, Mi, and So. The pattern then repeats, just an octave higher.

The first key is middle C, which is the center key on an 88 key piano. This pitch is too high for your low drone. To lower the pitch of the notes, scrub the left column downward until you reach G1.

This time, instead of tapping the screen to place a note, tap and drag to the right as shown below — this creates a a note that plays for the entire bar:

gadget-chicago

Tap on Sound Program and choose 028 Downer Arp — this changes the voice of the note played. This preset gives you a sound like a Roland Arp synthesizer with some modulation using the “arpeggiator” effect settings from the lower right:

007_Arpeggiator

Note: Playing the notes of a chord one at a time — in this case, the first, third, and fifth intervals — creates an arpeggio. A “downward” arpeggio starts with the highest note and plays the notes in descending order, while an “upward” arpeggio starts with the lowest note and plays the notes in ascending order.

Feel free to experiment with switches in the Arpeggiator section: try the Up, Up/Down, 2 Oct and 1 Oct settings to see how they change the songs. You can turn off the effect by hitting the On button in the Arpeggiator section of the console.

When you are finished experimenting, return the toggle switches to the Full and Down positions.

You can hear my version here for comparison purposes:

That takes care of the foundation tracks for your song — but most songs need a melody to carry them forward.

If that sounds complicated, consider this: most popular songs only have four to six notes total in the main melody, or “hook”! :]

Adding a Melody to your Song

Tap Back to return the Mixer view and tap the + button to add a third Gadget. Choose the Wolfburg Gadget and tap the gadget to switch to Edit view.

Select the 020 Syn. Trumpet instrument using the arrow keys at the top of the console. You’ll create a pattern with this synthesizer much in the same way you created a pattern with the London synthesizer.

Replicate the pattern you see in the image below:

Track 3 pattern

Each row in your grid now represents a different note, whereas with the London synthesizer each row represented a different instrument voice.

The A#3 note is played on the beat like the bass drum. I threw in a few C4 and D4 notes, some on the beat and some off the beat. On the fourth beat I add a little flare by playing F4, A#3 and G3 at the same time.

Here’s what I have so far:

Your song is quickly taking shape, but it still needs another element to pull everything together. To do this, you’ll add another low frequency drone to help fill in the bottom end.

Adding Another Low Drone

Tap Back to return to the Mixer view, and add another Gadget to your track by tapping the + button. Select the Berlin synthesizer, then enter the Edit view and set the Sound Program display to 005 Natural Lead using the + and - buttons.

Tap and drag a note across the entire measure at G1, much like you did with the other arpeggiated drone, as shown below:

track 4 pattern

Here’s the song up to this point:

You’ve covered the basics and created your first four-count bar of your song. Now it’s time to flesh this out and create a full song.

Stripping Down Your Song

When creating a song, I usually put together scenes that have a basic beat. I build the scene up as it progresses, then simplify it towards the end. Since I know the song will loop when I add it to my app, I’ll make the loop sound as smooth as possible by ending the track using the same beat with which I started.

So far, you have a single scene in your song. To flesh out your song and make it a little longer, you’ll duplicate the track you’ve already built, build the song up in the middle and then break it down towards the end. To accomplish this, you’ll use the Function button to copy, duplicate and delete the various elements of your song.

Have you been saving your work as you go along? If not, go back to the Mixer view, tap the document item and select Save to save your work in progress.

Ensure you’re in the Mixer view and tap the Function button in the lower left corner of the app. This overlays several commands on your screen. If the overlay looks different than the screenshot above, you may still be in the track’s Edit View.

In the top bar, tap Duplicate twice to create two copies of your scene as shown below:

IMG_0154

Tap Function to exit this function.

You should now see three identical scenes labelled 1, 2 and 3 as shown below:

IMG_0169

First, disable looping by tapping on the green loop icon in the bottom bar so that it turns gray. Then tap the play icon to play the song.

Your job now is to strip away some of the elements of your song near the beginning and end to make it sound more dynamic.

Tap the Function button to bring up the edit overlay. In Scene 1, tap Clear on Track 4 to clear the drone as shown below:

008_Clear

Tap Sure to confirm your action. Clear the drone from Scene 2 in the same way.

Tap Function again and you’ll see that the drone has been removed from both Scene 1 and 2 and only starts playing in Scene 3.

Press Play to hear how your song sounds now. Here’s my version up to this point:

You’re now going to modify your song so that it starts with the drum and arpeggio loops on their own in Scene 1 and adds the melody in Scene 2.

Tap Function to enter song edit mode again. Tap Clear on Track 3 of Scene 1 to clear the melody.

Press Play to hear how your song builds up now. My version is below:

You can definitely hear how it builds, but the scenes change much too quickly. You’ll fix that next.

Before moving on, now would be a great time time to Save your work.

Changing the Bar Count of a Scene

Each instrument in a Scene can be any number of bars long. Currently, each track in your song is only one bar in length — by adding more bars you can stretch out each scene and make the song build more slowly.

Tap Track 4 of Scene 1 to enter the track editor. The current length of the Scene is displayed at the top of the screen — 1 bar.

Tap the Function button and select 4 in the Bars segmented control to change the track length to four bars, like so:

IMG_0185

Tap Function to exit.

Tap Back to return to the Mixer View; you’ll see that Track 4 is now divided into 16 sections. Tap the loop button then tap Play to hear the changes.

Repeat the steps above to make each Scene four bars.

When you return to the Mixer view you’ll notice that the drone is only a quarter of the length of the bar as shown below:

IMG_0173

You want the drone to play over all four bars; you can use the Copy functionality to fix that.

Tap Track 4 in Scene 3; it opens to Bar 1 by default. Tap along the top of the bars to view the contents of Bars 2, 3 and 4. Press Function then press the grey Copy button on the overlay. Press 1 Bar to copy the notes from the first bar and the display will change to Select Destination as shown below:

IMG_0147

Tap 2 Bar to copy the contents of Bar 1 into Bar 2. Repeat the above steps to copy the contents of Bar 1 into Bars 3 and 4.

Tap Back to return to the Mixer view, hit Play and you’ll hear the drone play through all four bars, like so:

IMG_0176

Here’s my version of the song up to this point:

The song now builds more slowly, which sounds much better. Now you’ll need to mix things up a little in the song to keep it from feeling too monotonic.

Press Function in the main Mixer view and press Duplicate twice on Scene 3, and tap Function again to finish.

Then tap Track 4 on Scene 4 to edit the sequence, then drag the drone note from G1 up to A#1. This changes the pitch of the drone to set off this section a little bit. Repeat this for the second bar as well (or copy it over).

Tap Function then tap 2 to shorten the scene to two bars. Tap Back to return to the Mixer view. Repeat that step to shorten Scene 5 to two bars.

The overlay should now look like the following screenshot:

IMG_0177

Press Play to hear your composition. You might need to tap the green loop icon to turn off scene looping.

Here’s my version of the song for comparison purposes:

Now that the track builds nicely, you can break down the ending to make it sound good when it loops.

Breaking Down Your Song

Duplicate Scene 5 twice using the same steps as before. If things are getting a little tight and you need more space on the screen, tap the Expand icon to hide the instrument mixer:

IMG_0183

Use Function to clear the drone on Track 4 in Scenes 6 and 7. Clear the melody track in Scene 7 as well. Try to see if you can figure it out yourself without looking at the spoiler below!

Solution Inside: Solution SelectShow>

Duplicate Scene 7 to create Scene 8. Delete everything from Scene 8 except for the drum track. While you’re at it, modify Scene 6 and 7 to be four bars in length. Your finished piece should look like the following screenshot:

complete

Play your finished masterpiece all the way through; it sounds like you’re done!

Now that you have a great-sounding track, all you need to do is export it to an audio file and include it in your app.

Exporting to an Audio File

To export your song to file, press the Document icon and tap Export. You can export your song to GadgetCloud which is hosted on SoundCloud. Alternately, you can export your song as an Audio File using iTunes export, DropBox or AudioCopy, which lets you paste your composition into a compatible app.

Export the file to the destination of your choice. Now that you have a physical file you can work with, you can clean it up a little before using it in your game.

Trimming Your Song in QuickTime Player

You’ll likely need to trim your audio file a little as the export process adds a few seconds of dead air to the end of the file — this won’t sound very good when it loops!

Fortunately, it’s easy to trim your song in QuickTime player.

Open the file in QuickTime Player and set it to Loop under the View menu. Then select Trim from the Edit menu and adjust the length of your song. Listen carefully as the song loops; try to trim your song so that there isn’t a break or a choppy transition when the song loops.

Once you’re satisfied, you can Export the trimmed file. Choose Export from the File menu, then select Audio Only as the Format and save it as an m4a file.

If you’d like to see how I trimmed my file, you can check it out here: RW Game Track

All that’s left is to add the song to your app!

Adding Your Song to a Sprite Kit App

Note: This sectional is optional; Sprite Kit fans might find this part useful, but otherwise you’re done!

Download the completed SpriteKit Space Invaders app; if you’d like some background on the app you can read about it in this tutorial by Joel Shapiro.

Open GameScene.m in Xcode and add the following import to the top of the file:

@import AVFoundation;

Next add a private instance variable to store the music player as shown below:

@interface GameScene () {
    AVAudioPlayer *_backgroundMusicPlayer;
}

Add the following helper method to play the background music:

- (void)playBackgroundMusic:(NSString *)filename{
     NSError *error;
     NSURL *backgroundMusicURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
     _backgroundMusicPlayer = [[AVAudioPlayer alloc] 
          initWithContentsOfURL:backgroundMusicURL error:&error];
     _backgroundMusicPlayer.numberOfLoops = -1;
    [_backgroundMusicPlayer prepareToPlay];
    [_backgroundMusicPlayer play];
}

Finally, add the following to createContent in GameScene.m, immediately after the call to setupHud (change the filename to whatever you called yours):

[self playBackgroundMusic:@"RW Game Track.m4a"];

Note: In an app built with a standard Sprite Kit template, you could call the method in initWithSize: instead.

Build and run the app; you’ll hear the music playing as you play the game. Satisfying, isn’t it? :]

Once the game is over, the music will stop playing as the Sprite Kit scene is replaced.

Where To Go From Here?

Here is the finished RW Game Track.gdproj. You can add it to your own Korg Gadget app with iTunes sharing.

To further refine your tune, you can tweak the notes in each scene to simplify or enhance your tracks. Experiment with adding notes and drones in different places. Try the Amsterdam gadget to add some interesting sounds and effects. Most of all, have fun and get creative!

If you have any questions or comments, or would like to share your creations with us, feel free to join the discussion below!

How to Make Game Music for Beginners is a post from: Ray Wenderlich

The post How to Make Game Music for Beginners appeared first on Ray Wenderlich.

How to Add Table View Search in Swift

$
0
0
UISearchBar-square

So much data, so little time.

Update note: This tutorial was updated to iOS 8 and Swift by Brad Johnson. Original post by Tutorial Team Member Nicolas Martin.

In the mobile app world, people want their information fast, and they want it now!

iOS users expect their data to be available on-demand and presented quickly. Furthermore, they expect this to all happen in an easy to use and intuitive manner. It’s a tall order, to be sure!

Many UIKit based applications use the UITableView as a way to navigate through data, since scrolling is natural and fast. But what about the cases where there are large, even huge, amounts of data to sift through? With large datasets, scrolling through massive lists becomes slow and frustrating – so it’s vitally important to allow users to search for specific items. Lucky for us, UIKit includes UISearchBar which seamlessly integrates table view search and allows for quick, responsive filtering of information.

In this tutorial, you’ll build a searchable Candy app which is based on a standard table view. You’ll add table view search capability, including dynamic filtering and adding an optional Scope Bar. In the end, you’ll know how to make your apps much more user friendly and satisfy your users’ urgent demands!

Ready for some sugar-coated search results? Read on!

Note: At the time of writing this tutorial, our understanding is we cannot post screenshots of beta software. All the screenshots here are from iOS 7 and we are suppressing Xcode 6 screenshots until we are sure it is OK.

Getting Started

In Xcode, create a new project by going to File\New\Project…. Choose Single View Application and click Next. Name the project CandySearch and make sure that Language is set to Swift and Devices is set to iPhone. Click Finish, browse to the location you want to create the project and then click Create.

Start by clearing out some of the default files so you can truly start from scratch. In the Project Navigator, select ViewController.swift, right-click, select Delete, and then click Move to Trash. Then open up Main.storyboard, select the only view controller and delete it. Now that you have an empty storyboard to work with, you can add the main screens in your application.

From the Object Browser (the lower half of the right sidebar) drag out a Navigation Controller to add iOS’s built-in navigation logic to the project. This will create two view controllers on the storyboard – the navigation controller itself and a table view controller inside that will be the initial view for the application.

You’ll need one more view controller as the detail view controller that is displayed when the user selects an item from the list. Drag a View Controller object onto the storyboard. Control-drag from the Table View Controller to the new view controller and select show as the manual segue from the popup.

Setting Up the Candy Class

Next you will create a struct to model the information about each piece of candy you’re displaying, such as its category and name. To do this, right click on the CandySearch folder and select New File…. Select iOS \ Source \ Swift File and click Next. Name the file Candy.swift. Open the file, and add the following definition:

struct Candy {
  let category : String
  let name : String
}

This struct has two properties: the category and name of the candy. When the user searches for a candy in your app, you’ll be referencing the name property against the user’s search string. You’ll see how the category string will become important near the end of this tutorial when you implement the Scope Bar.

You don’t need to add your own initializer here since you’ll get an automatically-generated one. By default, the initializer’s parameters will match up with properties you define in the same order. You’ll see how to create candy instances in the next section.

Now you are ready to set up the UITableView that your UISearchBar will filter!

Connecting the Table View

Next you will set up a UITableView that will work with the UISearchBar. Create a new file by right clicking on the CandySearch folder and selecting New File…. Select iOS \ Source \ Cocoa Touch Class and click Next. Name the class CandyTableViewController, make it a subclass of UITableViewController and set the Language to Swift.

You will start by adding an array for the sample data. Open CandyTableViewController.swift and add the following code inside the class definition:

var candies = [Candy]()

The candies array will be where you manage all the different Candy objects for your users to search. Speaking of which, it’s time to create some Candy! In this tutorial, you only need to create a limited number of values to illustrate how the search bar works; in a production app, you might have thousands of these searchable objects. But whether an app has thousands of objects to search or just a few, the methods used will remain the same. Scalability at it’s finest! To populate your candies array, override the viewDidLoad as follows:

override func viewDidLoad() {
  super.viewDidLoad()
 
  // Sample Data for candyArray
  self.candies = [Candy(category:"Chocolate", name:"chocolate Bar"),
    Candy(category:"Chocolate", name:"chocolate Chip"),
    Candy(category:"Chocolate", name:"dark chocolate"),
    Candy(category:"Hard", name:"lollipop"),
    Candy(category:"Hard", name:"candy cane"),
    Candy(category:"Hard", name:"jaw breaker"),
    Candy(category:"Other", name:"caramel"),
    Candy(category:"Other", name:"sour chew"),
    Candy(category:"Other", name:"gummi bear")]
 
  // Reload the table
  self.tableView.reloadData()
}

This code doesn’t do much yet, but it does some important set up for later. First, it populates the candies array with 9 different candies and categories. You’ll use this array later when you populate the table. Second, you tell the tableView that it should reload it’s data. You need to do this to ensure all the code you are about to write gets called after the candies array is populated.

Next, you’ll add functions that govern the table view itself. Implement the tableView(_:numberOfRowsInSection:) like so:

override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
  return self.candies.count
}

This simply tells the tableView that it should contain as many rows as there are items in the candies array you populated earlier.

Now that the tableView knows how many rows to use, you need to tell it what to put in each row. You’ll do this by implementing tableView(_:cellForRowAtIndexPath:):

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
  //ask for a reusable cell from the tableview, the tableview will create a new one if it doesn't have any
  let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
 
  // Get the corresponding candy from our candies array
  let candy = self.candies[indexPath.row]
 
  // Configure the cell
  cell.textLabel.text = candy.name
  cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
 
  return cell
}

This function has three main steps. First, you dequeue a cell to use for this indexPath. You then access the candies array, reference the indexPath to decide which Candy object to pull, and then use that Candy object to populate the UITableViewCell.

Note:If you’ve been around Table Views before, you may be wondering why you didn’t have to implement heightForRowAtIndexPath. As of iOS 8, this method is no longer required; the OS will determine the size of your cells at runtime to determine the height. Wasn’t that nice of them? :] Of course, if your app is going to support an earlier version of iOS, you will need to implement this method.

You’re almost there! There’s one last step you’ll need to take before you can see your list of candies. You need to hook up all your code to your storyboard in Interface Builder. First, open Main.storyboard and select the Root View Controller. In the Identity Inspector (third tab on the top half of the right sidebar), set the Class to CandyTableViewController. This will tell the OS to load your custom class when this controller is pushed on the screen. While you’re at it, double click the Root View Controller title and change it to CandySearch to give your users an idea of what they’re doing.

Finally, you need to tell the OS what cell to use when your code looks for a new one. You may have noticed when you created the storyboard earlier that a default cell was placed in the table view. Select the cell (conveniently named “Cell”) and open the Attributes Inspector (just to the right of the Identity Inspector). Change the Identifier to Cell. This matches the reuse identifier you used in your code earlier to dequeue the cell.

Save your changes and build and run. You now have a working table view! So much candy…so little time! We need…a UISearchBar!

UISearchBar-nosearch

Setting Up the UISearchBar

If you look at the UISearchBar documentation, you’ll discover it’s pretty lazy. It doesn’t do any of the work of searching at all! The class simply provides a standard interface that users have come to expect from their iOS apps. It’s more like a middle-class manager in that respect; it’s great at delegating tasks to others.

The UISearchBar class communicates with a delegate protocol to let the rest of your app know what the user is doing. All of the actual functions for string matching and other operations will be written by you. Although this may seem a tad scary at first (and more than a little unfair!), writing custom search functions gives you tight control over how results are returned specifically in your app. Your users will appreciate searches that are intelligent — and fast.

Start by opening up Main.storyboard and dragging a Search Bar and Search Display Controller object to the table view controller. Be careful — this is different from the Search Bar object, which is also available. Position the Search Bar between the Navigation bar and the Table View.

Not sure what’s meant by a search display controller? According to Apple’s own documentation, a search display controller “manages display of a search bar and a table view that displays the results of a search of data managed by another view controller.”

That means the search display controller will have its own table view to display the results, separate from the one you just set up. So basically, the search display controller added above handles the task of showing the filtered data from a search in a separate view controller that you don’t have to worry about. :]

UISearchBar Options in the Attributes inspector

While in the storyboard, take a moment to review the properties available for the Search Bar object. You may not use all of them, but knowing what’s available in the Attributes Inspector is always valuable when working with a new UIKit component.

  • Text: This will change the actual string value that is present in the search bar. As your app has no need to have a default value in the search bar, you won’t need this.
  • Placeholder: This does exactly what you might expect – it allows you to put the light gray text in the Search bar that tells the user what the search bar can be used for. In the case of the Candy app, use “Search for Candy”.
  • Prompt: This text will appear directly above the search bar. This is good for apps that have complicated search mechanisms, where the user might need instructions. (But in this app, the Placeholder text should be pretty clear!)
  • Search Style, Bar Style, Translucent, Tint, Background and Scope Bar Images: These options allow you to customize the appearance of your search bar. The options are almost identical to those of the UINavigationBar and it is normally advisable to have your UISearchBar match your UINavigationBar for harmonious design.
  • Search Text and Background Positions: These options allow you to add a custom offset to both your search text and background image.
  • Show Search Results Button: Provides a button on the right side of the search bar for performing functions such as displaying recent searches or showing the last search results. Interaction with this button is managed through the Search Bar Delegate methods.
  • Show Bookmarks Button: Shows the standard blue oval bookmarks icon in the right hand side of the search bar. Users expect this to bring up a list of their saved bookmarks. Like the search results button, this is also managed through the Search Bar Delegate methods.
  • Show Cancel Button: This button allows users to close the separate view controller that is generated by the search bar. Leave this unchecked, as the search bar will automatically show and hide the Cancel button when the user is in Search mode.
  • Shows Scope Bar & Scope Titles: The scope bar allows users to refine their search by limiting the results to a certain category, or scope. In a music application, for example, this bar may show choices such as Artists, Albums or Genres. For now, leave this box unchecked; you will implement your own scope bar later in this tutorial.
  • Capitalize, Correction, Keyboard, etc.: These are options that have been borrowed from the normal UITextField options and allow you to change your search bar’s behavior. For example, if the user will be searching on proper nouns like businesses or last names, you may want to consider turning off correction as it will be annoying to users. In this tutorial, the candy names are all common nouns, so the default options will suffice.
Note: Knowing the available options allows you to reduce development time and be more efficient. So as a general note for your future iOS development, always take the time to get acquainted with the resources available :]

UISearchBarDelegate and Filtering

After setting up the storyboard, you’ll need to do some coding work to get the search bar working. To allow the CandyTableViewController class to respond to the search bar, it will have to implement a few protocols. Open CandyTableViewController.swift and replace the class declaration with the following:

class CandyTableViewController : UITableViewController, UISearchBarDelegate, UISearchDisplayDelegate {

UISearchBarDelegate defines the behavior and response of a search, while UISearchDisplayDelegate defines the look and feel of the search bar.

Next, add the following property to the class:

var filteredCandies = [Candy]()

This array will hold the filtered search results.

Next, add the following helper method to the class:

func filterContentForSearchText(searchText: String) {
  // Filter the array using the filter method
  self.filteredCandies = self.candies.filter({( candy: Candy) -> Bool in
    let categoryMatch = (scope == "All") || (candy.category == scope)
    let stringMatch = candy.name.rangeOfString(searchText)
    return categoryMatch && (stringMatch != nil)
  })
}

This method will filter the candies array based on searchText (which is the search string entered by the user), and will put the results in the filteredCandies array. Swift Arrays have a method called filter() that takes a closure expression as its only parameter.

If you have some experience with Objective-C, think of closure expressions as Swift’s version of blocks. A closure is a self contained block of functionality. They are called closures because they can capture and store references to any variables or constants that are created or used in the same context the closure is created, which is known as closing. Here is the syntax for a closure expression:

{(parameters) -> (return type) in expression statements}

In this case, the filter method uses the closure expression separately on each element of the array being filtered. The parameter of the closure expression is the individual element being sorted. The closure expression returns a Bool that is true if the element should be included in the new filtered array, or false if the element should not be included. If you look at the documentation of this method, you’ll notice it uses a type referred to as <T>. This is a generic type, which means it can be any type. Since you are defining you’re own filter rules inside the closure expression, this filter method will work on an array filled with any kind of type. The closure expression’s parameter is also of type T, which is the element being filtered. In your code you replaced that with candy: Candy, since you know this array is filled with Candy objects.

rangeOfString() checks if a string contains another string. If it does and the category also matches, you return true and the current candy is included in the filtered array; if it doesn’t you return false and the current candy is not included.

Next, add the following two methods to the class:

func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
  self.filterContentForSearchText(searchString)
  return true
}
 
func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchScope searchOption: Int) -> Bool {
  self.filterContentForSearchText(self.searchDisplayController.searchBar.text)
  return true
}

These two methods are part of the UISearchDisplayControllerDelegate protocol. They will call the content filtering function when the the user enters a search query. The first method runs the text filtering function whenever the user changes the search string in the search bar. The second method will handle the changes in the Scope Bar input. You haven’t yet added the Scope Bar in this tutorial, but you might as well add this UISearchBarDelegate method now since you’re going to need it later.

Build and run the app now; you’ll notice that using the Search Bar still does not bring up any filtered results! What gives? This is simply because you haven’t yet written the code to let the tableView know when to use the normal data vs. the filtered data. You’ll need to modify both the numberOfRowsInSection and cellForRowAtIndexPath functions. Replace the functions with the new implementations below:

override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
  if tableView == self.searchDisplayController.searchResultsTableView {
    return self.filteredCandies.count
  } else {
    return self.candies.count
  }
}
 
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
  //ask for a reusable cell from the tableview, the tableview will create a new one if it doesn't have any
  let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
 
  var candy : Candy
  // Check to see whether the normal table or search results table is being displayed and set the Candy object from the appropriate array
  if tableView == self.searchDisplayController.searchResultsTableView {
    candy = filteredCandies[indexPath.row]
  } else {
    candy = candies[indexPath.row]
  }
 
  // Configure the cell
  cell.textLabel.text = candy.name
  cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
 
  return cell
}

This code tests to see if the currently displayed tableView is the search table or the normal table. If it is indeed the search table, the data is taken from the filteredCandies array. Otherwise, the data comes from the full list of items. Recall that the search display controller automatically handles showing and hiding the results table, so all your code has to do is provide the correct data (filtered or non-filtered) depending on which table view is currently displaying.

Build and run the app. You’ve got a functioning Search Bar that filters the rows of the main table! Huzzah! Play with the app for a bit to see how the user can search for various candies.

UISearchBar-somesearch

You’ve probably noticed that that the if/else logic found in the tableView(numberOfRowsInSection:) method is reused quite a few times. This is important when working with the Search Bar Display Controller and omitting this if/else check may result in bugs that will be difficult to track down. Just remember that the filtered results do not appear in the same table view as the main table. They are actually completely separate table views, but Apple has designed them in such a way that the experience is seamless for the end user — at the expense of being confusing to the novice developer!

Sending Data to a Detail View

When sending information to a detail view controller, you need to ensure that the view controller knows which table view the user is working with: the full table list, or the search results. The code for this will be similar to the code that you wrote in tableView(_:numberOfRowsInSection:) and tableView(_:cellForRowAtIndexPath:). Still in CandyTableViewController.swift, add the following methods to the class:

override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
  self.performSegueWithIdentifier("candyDetail", sender: tableView)
}
 
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
  if segue.identifier == "candyDetail" {
    let candyDetailViewController = segue.destinationViewController as UIViewController
    if sender as UITableView == self.searchDisplayController.searchResultsTableView {
      let indexPath = self.searchDisplayController.searchResultsTableView.indexPathForSelectedRow()
      let destinationTitle = self.filteredCandies[indexPath.row].name
      candyDetailViewController.title = destinationTitle
    } else {
      let indexPath = self.tableView.indexPathForSelectedRow()
      let destinationTitle = self.candies[indexPath.row].name
      candyDetailViewController.title = destinationTitle
    }
  }
}

Open up the storyboard and make sure that the segue from the Candy Table View Controller to the Detail View has the identifier candyDetail. Build and run the code at this point and see how the app now navigates to the Detail View from either the main table or the search table with ease.

Creating an Optional Scope Bar to Filter Results

If you wish to give your users another way to filter their results, you can add a Scope Bar in conjunction with your search bar in order to filter out items by their category. The categories you will filter on are the ones you assigned to the Candy object when the candyArray was created: chocolate, hard, and other.

First, set up the scope bar on the storyboard. Go to the CandySearch View Controller and select the search bar. In the attributes inspector, check Shows Scope Bar in the Options section. Then modify the Scope Titles to be: “All”, “Chocolate”, “Hard” ,and “Other”. (You can use the + button to add more scope items and if you double-click an item, you can edit the item title).

Next, modify filterContentForSearchText in CandyTableViewController.swift to take the new scope into account. Replace the current method implementation with the following:

func filterContentForSearchText(searchText: String, scope: String = "All") {
  self.filteredCandies = self.candies.filter({( candy : Candy) -> Bool in
  var categoryMatch = (scope == "All") || (candy.category == scope)
  var stringMatch = candy.name.rangeOfString(searchText)
  return categoryMatch && (stringMatch != nil)
  })
}

This method now takes in an option scope variable (it has a default value of “All” if nothing is passed in). The method now checks both the scope and the string, and only returns true if both the category and the string match (or the category is set to “All”).

Now that you’ve modified this method, you’ll need to change the two searchDisplayController methods to account for the scope:

func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
  let scopes = self.searchDisplayController.searchBar.scopeButtonTitles as [String]
  let selectedScope = scopes[self.searchDisplayController.searchBar.selectedScopeButtonIndex] as String
  self.filterContentForSearchText(searchString, scope: selectedScope)
  return true
}
 
func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchScope searchOption: Int) -> Bool {
  let scope = self.searchDisplayController.searchBar.scopeButtonTitles as [String]
  self.filterContentForSearchText(self.searchDisplayController.searchBar.text, scope: scope[searchOption])
  return true
}

Both methods now pull the scope out of the search bar and pass it to the new filter method. Build and run now. Here’s how your scope bar should look:

UISearchBar-scope

Where To Go From Here?

Congratulations – you now have a working app that allows you to search directly from the main table view! Here is a sample project with all of the code from the above tutorial.

Table views are used in all kinds of apps, and offering a search option is a nice touch for usability. With UISearchBar and the UISearchDisplayController, iOS provides much of the functionality out of the box so there’s no excuse for not using it. The ability to search a large table view is something that today’s users expect; when they find it isn’t present, they won’t be happy campers!

Don't let this happen to your users. Always give them a search option.

Don’t let this happen to your users. Always give them a search option.

I hope to see you adding search capabilities to your table view apps in the future. If you have any questions or comments, please join the forum discussion below!

How to Add Table View Search in Swift is a post from: Ray Wenderlich

The post How to Add Table View Search in Swift appeared first on Ray Wenderlich.


How to Port Your Sprite Kit Game from iOS to OS X

$
0
0
Learn how to port your Sprite Kit games from iOS to OS X!

Learn how to port your Sprite Kit games from iOS to OS X!

Have you ever wished you could port your iOS Sprite Kit game to OS X? Surprisingly, it’s easier than you think.

Apple developed Sprite Kit with the goal of keeping iOS and Mac OS X development as identical as possible – which makes it extremely easy to develop for both environments simultaneously.

This tutorial will show you how to take an existing iOS Sprite Kit game — the finished project from Sprite Kit Tutorial for Beginners — and adapt it to Mac OS X.

You’ll learn how to maintain both versions in a single Xcode project and how to continue development of your game without copying and pasting heaps of code to keep both versions up to date.

It’s recommended that you have some familiarity with Mac OS X Development if you choose to work through this tutorial. However, one of the wonderful features of Sprite Kit is that you don’t need to have a lot of prior experience to turn out a great app.

If do want to learn more about Mac development, you can check out this three part tutorial on making a simple Mac app.

Getting Started

Download the starter project for this tutorial here, and build and run on your iPhone to try it out.

The starter project has a few changes from the original in order to showcase several differences between the iOS and OS X versions of the game.

Accelerometer Support

This project borrows accelerometer support from the Space Game Starter Kit which lets the player move their character up and down the screen by tilting the device, like so:

2014-04-08 13_56_00

More Destructive Power

The ninja star has now been imbued with magical ninja powers of epic destruction!

2014-04-08 14_20_49

Ok, maybe not that epic, but this version uses a nice particle effect to give the ninja star some visual impact.

Small Technical Improvements

Vicki Wenderlich lent her talents to the game and added a new tiled background which makes the game more aesthetically pleasing. As well, the status bar is hidden and the app now supports iPad resolutions.

Working with Projects and Targets

A project file ties all of your working files together, but what determines how your project is compiled is a target.

A single project can contain multiple targets. This lets you compile your project in several different ways, depending on which files are associated with a specific target and the specific build settings of the target. This should give you a clue as to how you’re going to set up this project to compile for OS X!

Let’s take a look at this project’s targets. Open the SpriteKitSimpleGame project, and at the top of the left panel, select your project to show the project settings. Then select SpriteKitSimpleGame in the project window.

Right now there are two targets, one for iOS and the other for iOS unit tests, as shown below:

Screen Shot 2014-04-08 at 10.26.37 pm

Alternatively, you can select SpriteKitSimpleGame from the Target list if the project and target list are both expanded:

Screen Shot 2014-04-13 at 11.32.19 am

When you build an application, you build it for the device or devices supported by the target. To see which devices the current target supports, click the SpriteKitSimpleGame drop down next to the Stop button at the top-left of the window, as shown below:

Screen Shot 2014-04-08 at 10.30.12 pm

This is where you’ll add a build target for Mac OS X.

Adding a Build Target for Mac OS X

Ensure your Xcode project is the active window and select File \ New \ Target as shown below:

Screen Shot 2014-04-08 at 10.33.15 pm

Xcode prompts you to select a template for your new target.

Select the OS X\Application\SpriteKit Game template and click Next, as shown below:

Screen Shot 2014-04-08 at 10.36.00 pm

Finally, type in the product name as SpriteKitSimpleGameMac and click Finish, like so:

Screen Shot 2014-04-08 at 10.37.25 pm

Note: If you plan to release your app on the Mac App Store, your bundle identifier must be registered separately on developer.apple.com. Profiles and provisioning are arranged separately for iOS and Mac OS X developer programs.

To try running your app on OS X, select SpriteKitSimpleGameMac from the scheme list, then select My Mac 64-bit as shown below:

Screen Shot 2014-04-08 at 10.43.07 pm

What do you think will happen when you try to build and run your project using this new target?

  • A — The game will run perfectly — and that’s the end of the tutorial!
  • B — The game will fail to compile with numerous errors.
  • C — The game will compile but crash on launch.
  • D — The game will compile and run but won’t be the game you expected.

Solution Inside: Answer SelectShow>

Structuring Your Files for Multiple Targets

Now that you have a Mac OS X build target, you’ll be modifying and adding files to make the app work under OS X. Keeping track of these files can be difficult as time goes on, so it’s best to set up a system right now to help keep all the files organized.

Minimize all of your groups and add a new group named SharedResources as shown below:
Screen Shot 2014-04-08 at 11.02.12 pm

This group will be the main location for your game and will contain resources that are common to both Mac OS X and iOS targets.

While you’re at it, create a separate group named Testing and move the test modules into it, like so:

Screen Shot 2014-04-08 at 11.08.13 pm

Keeping the unit tests in a separate folder helps avoid visual clutter.

Now that you have your new organized structure for the various files in your project, you’ll need to move the files into the appropriate locations.

Expand SharedResources and SpriteKitSimpleGame. Click and drag the Particles and Sounds groups from SpriteKitSimpleGame to SharedResources.

Next, drag over the sprites.atlas folder, MyScene.h, MyScene.m, GameOverScene.h and GameOverScene.m. Your file structure should look like the one shown below:

Screen Shot 2014-04-08 at 11.17.26 pm

Delete the Spaceship.png file — you won’t need that any longer. This is just a boilerplate file that is automatically added when you create a game with the Sprite Kit template.

All the shared files of your Sprite Kit game now reside in the SharedResources group. Everything left in the SpriteKitSimpleGame relates to launching and managing the game on iOS.

Expand the SpriteKitSimpleGameMac group. You’ll need to remove the example game files from this group before you progress any further.

Delete MyScene.h, MyScene.m and Spaceship.png file from the SpriteKitSimpleGameMac group and select Move to Trash. Your file list should look like so:

Screen Shot 2014-04-08 at 11.22.52 pm

Note: You may have noticed that the Mac version of your game does not contain a ViewController class; instead, it only has an AppDelegate. The UIViewController class is part of UIKit which is not available on Mac OS X. Instead, the AppDelegate creates an instance of NSWindow which will present your Sprite Kit scene.

As a final check, your fully-expanded file list should look like the following:

Screen Shot 2014-04-08 at 11.27.35 pm

You’ve removed unnecessary files from the project and organized it neatly. Now it’s time to modify your targets to let the compiler know which files to include with each target.

Adding Target Membership

Expand the Frameworks group and select UIKit.framework, like so:

Screen Shot 2014-04-08 at 11.39.04 pm

Expand the right Utilities Panel select File Inspector, like so:

Screen-Shot-2014-04-08-at-11.40.23-pm-A

About halfway down the File Inspector you’ll see the Target Membership section. This is where you select the targets that will use this file.

UIKit is only available on the iOS platform, so leave it unchecked on your Mac OS X targets, as shown below:

Screen Shot 2014-04-08 at 11.40.54 pm

The Cocoa Framework is only available on Mac OS X so ensure it’s checked for your Mac targets and unchecked for your iOS targets like so:

Screen Shot 2014-04-08 at 11.49.11 pm

The Sprite Kit Framework is available to iOS and Mac OS X so set the target membership as below:

Screen Shot 2014-04-08 at 11.50.20 pm

Each individual file in your project has its own target membership with the exception of texture atlases. The atlas itself is the only place you need to set a target membership and all contained textures will be automatically included.

However, classes work a little differently. You can’t set a target membership on a .h file — instead, you must set the target membership on the .m file.

Armed with your new-found understanding of target membership, work through each file in your SharedResources group and make sure both SpriteKitSimpleGame and SpriteKitSimpleGameMac are ticked on each file. In total, you should need eight ticks to get the job done.

Next, work through each file in your SpriteKitSimpleGame group and make sure that only SpriteKitSimpleGame is ticked for each — they should all be set correctly at this point, but it’s good to check.

Finally, work through each file in SpriteKitSimpleGameMac group and make sure that only SpriteKitSimpleGameMac files are ticked. Again, you shouldn’t have to change any but it never hurts to check.

Now that your project is properly set up for your iOS and Mac targets, you can get down to what you’re good at — writing code!

Getting the Game to Build and Run

As it stands right now, your project will still build and run without issue for iOS. The changes you just made have no real effect on the existing game. However, if you build and run the Mac target, you’ll see a bunch of errors. That’s because you haven’t yet accounted for the differences between iOS and OS X.

Build and run your project using the SpriteKitSimpleGameMac target; what do you see?

You’ll receive the error Module ‘CoreMotion’ not found. Mac OS X doesn’t have a CoreMotion class or its equivalent; you’ll have to work around this issue and use the keyboard to control player movement. However, your primary goal is to get the project to a buildable state before you worry about implementation details like that.

But how will you fix this? You can’t just remove the line of code referring to CoreMotion, otherwise the iOS version will break. You can’t work around this by using an if statment, since the compiler will still check each line of the code and throw an error if it doesn’t recognize something.

Open MyScene.m and replace:

@import CoreMotion;

with the following code:

#if TARGET_OS_IPHONE
@import CoreMotion;
#endif

Unlike a regular if statement, an #if is performed by the preprocessor. TARGET_OS_IPHONE returns TRUE if the current target is iOS.

Note: If you are plan to use an #if statement to check if the target OS is Mac OS X then the preferred method to check this is !TARGET_OS_IPHONE.

TARGET_OS_MAC seems to work — but the problem is that it also returns TRUE for iOS.

This might seem odd, but Apple uses !TARGET_OS_IPHONE in their example projects that contain multiple targets, so if it is a glitch, it’s most likely one they don’t plan to fix.

Now you will need to find the remaining code related to CoreMotion and surround it with #if statements.

Find the following code in the instance variables for MyScene.m:

CMMotionManager *_motionManager;

…and replace it with:

#if TARGET_OS_IPHONE
  CMMotionManager *_motionManager;
#endif

Scroll down to the init method and find the following code:

_motionManager = [[CMMotionManager alloc] init];
_motionManager.accelerometerUpdateInterval = 0.05;
[_motionManager startAccelerometerUpdates];

Replace the above code with the following:

#if TARGET_OS_IPHONE
_motionManager = [[CMMotionManager alloc] init];
_motionManager.accelerometerUpdateInterval = 0.05;
[_motionManager startAccelerometerUpdates];
#endif

Now, find the following code [TODO: FPE: Again, are we in the same file?]:

[self updatePlayerWithTimeSinceLastUpdate:timeSinceLast];

…and replace it with the following:

#if TARGET_OS_IPHONE
[self updatePlayerWithTimeSinceLastUpdate:timeSinceLast];
#endif

Last but not least, locate the method named updatePlayerWithTimeSinceLastUpdate: and wrap the entire method with the following code:

#if TARGET_OS_IPHONE
- (void)updatePlayerWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast 
.
.
.
}
#endif

If you build this against an iOS target, all of the above #if statements will return TRUE, so the app compiles just as it did before. In contrast, if you build this against a Mac OS X target all of the above #if statements will return FALSE and none of the above blocks will be compiled.

Take a look at the touchesEnded:withEvents: method in MyScene.m. Since the current version of Mac OS X doesn’t support touch screens, this method is meaningless. The Mac OS X version of this game will use mouse clicks instead as a perfectly adequate substitute for screen touches.

To avoid adding a bunch of boilerplate code, you’ll now create a class that inherits from SKScene to help you handle both screen touches and mouse clicks!

Adding Event Handlers

Select your SharedResources group.

On the menu bar select File \ New \ File… as shown below:

Screen Shot 2014-04-14 at 12.19.00 am

Select Objective-C Class from either the iOS or OS X category and click Next.

Screen Shot 2014-04-09 at 3.16.00 pm

Name the file SKMScene and make it a subclass of SKScene.

Screen Shot 2014-04-09 at 3.19.05 pm

Place the file directly under your project folder, make sure both iOS and Mac targets are ticked in the target area and click Create.

Screen Shot 2014-04-09 at 3.20.23 pm

Open SKMScene.h and replace its contents with the following code:

@import SpriteKit;
 
@interface SKMScene : SKScene
 
//Screen Interactions
-(void)screenInteractionStartedAtLocation:(CGPoint)location;
-(void)screenInteractionEndedAtLocation:(CGPoint)location;
 
@end

You’ll override the above two screen interaction methods using subclasses of SKMScene.

Add the following code to SKMScene.m directly under the line @implementation SKMScene:

#if TARGET_OS_IPHONE
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  UITouch *touch = [touches anyObject];
  CGPoint positionInScene = [touch locationInNode:self];
  [self screenInteractionStartedAtLocation:positionInScene];
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
  UITouch *touch = [touches anyObject];
  CGPoint positionInScene = [touch locationInNode:self];
  [self screenInteractionEndedAtLocation:positionInScene];
}
 
- (void)touchesCancelled:(NSSet *)touches
               withEvent:(UIEvent *)event
{
  UITouch *touch = [touches anyObject];
  CGPoint positionInScene = [touch locationInNode:self];
  [self screenInteractionEndedAtLocation:positionInScene];
}
#else
-(void)mouseDown:(NSEvent *)theEvent {
  CGPoint positionInScene = [theEvent locationInNode:self];
  [self screenInteractionStartedAtLocation:positionInScene];
}
 
- (void)mouseUp:(NSEvent *)theEvent
{
  CGPoint positionInScene = [theEvent locationInNode:self];
  [self screenInteractionEndedAtLocation:positionInScene];
}
 
- (void)mouseExited:(NSEvent *)theEvent
{
  CGPoint positionInScene = [theEvent locationInNode:self];
  [self screenInteractionEndedAtLocation:positionInScene];
}
#endif
 
-(void)screenInteractionStartedAtLocation:(CGPoint)location {
  /* Overridden by Subclass */
}
 
-(void)screenInteractionEndedAtLocation:(CGPoint)location {
  /* Overridden by Subclass */
}

That’s a fair bit of code, but if you read through from the top, it makes a lot of sense. Touching the screen or the end of a touch event calls the methods in the #if TARGET_OS_IPHONE block. You then create a CGPoint containing the pixel location of the touch and calla the relevant screenInteraction method.

Pressing a mouse button or releasing the mouse button calls the methods in the #else section. Similar to above, you create a CGPoint containing the pixel location of the touch and call the relevant screenInteraction method.

The advantage of using this subclass is that both touch and click events call a screenInteraction method. The screenInteraction methods have no code as you’ll override these in your subclass.

Open MyScene.h and add the following class declaration just under #import :

#import "SKMScene.h"

Next, update the superclass in the @interface line to SKMScene as shown below:

@interface MyScene : SKMScene

This ensures your game scene inherits from your SKMScene subclass. You can now substitute your subclasses for the touch events.

In MyScene.m find the following line:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

…and replace it with the following:

-(void)screenInteractionEndedAtLocation:(CGPoint)location {

Next, delete the following lines from the method as you won’t need them any longer:

UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];

Build and run your project using the Mac OS X target; it should compile and run without too many issues:

Screen Shot 2014-04-09 at 3.50.23 pm

Congratulations — you’re now running your Sprite Kit game on your Mac! You’ll notice that it has a few bugs:

  1. Some Macs have a noticeable pause the first time you click on the screen.
  2. The particle effect appears to be a little broken on some Macs.
  3. Not everything is sized correctly on the screen.
  4. The background music has gone noticeably silent.

You’ll tackle each of these bugs in turn — and you’ll learn about a few of the common bugs you’ll encounter when you convert apps between platforms.

Correcting Pre-loading Issues

This bug may not raise its head on all systems, but when it does it definitely points to a performance issue. “First-time-through” bugs like this usually stem from the initial load of resources into memory.

Texture atlases are the first resource type that springs to mind, but since this app doesn’t contain animations or large complex images, it’s safe to assume the problem is somewhere else.

The sound effects are the next most likely candidate as the sound files don’t get loaded until the user clicks on the screen.

To fix this, add the following instance variable to MyScene.m:

SKAction *_playPewPew;

Next, add the following line to initWithSize: inside the if statement:

_playPewPew = [SKAction playSoundFileNamed:@"pew-pew-lei.caf" waitForCompletion:NO];

This modifies your app to preload the sound file when the scene initializes.

Find the following line in screenInteractionEndedAtLocation::

[self runAction:[SKAction playSoundFileNamed:@"pew-pew-lei.caf" waitForCompletion:NO]];

…and replace it with the following:

[self runAction:_playPewPew];

Build and run your app; click the mouse and ensure that the delay has been eradicated.

If your system didn’t expose this bug, then at least the changes above will ensure that it won’t happen on someone else’s system.

Correcting SKS Issues

2014-04-14 21_44_29

At the time of this writing the bug in the particle effect seems to be an issue with Xcode 5. You’ll override the file reference to the texture in your sks file.

Technically, there isn’t anything wrong with your sks file – and you won’t experience this issue on all systems – but you should fix it nonetheless.

Find the following line in projectile:dideCollideWithMonster: of MyScene.m:

SKEmitterNode *emitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:@"SmallExplosion" ofType:@"sks"]];

Add the following code directly under the line you found above:

emitter.particleTexture = [SKTexture textureWithImageNamed:@"spark"];

All you have done above is to tell Xcode where to find the particular texture.

Build and run your app; now you can admire your epic glitch-free particle effect.

Correcting Image Resizing Issues

Navigate to your SpriteKitSimpleGameMac group and then to AppDelegate.m. Take a look at the screen size set in applicationDidFinishLaunching:.

It’s set to 1024 x 768 — this is the resolution of the non-Retina iPad.

Now take a look at the contents of sprites.atlas. As expected, all iPad versions of images are suffixed with ~ipad so that your app knows to use these images when it runs on an iPad.

Unfortunately, there is no ~mac suffix you can use here; instead, you’ll need to create a separate texture atlas for the Mac version of your app.

In order to keep your build as small as possible, you should use a texture atlas with only the resolutions your app will actually use.

Right-click on sprites.atlas and select Show in Finder to take you to the images folder.

Create a copy of sprites.atlas and delete all images from the copied folder that don’t have ~ipad in their name.

Screen Shot 2014-04-14 at 9.56.44 pm

Next, remove the ~ipad designator from the file names but leave the @2x designator intact.

Note: The @2x files have been left in the project to support the Retina screens on the Macbook Pro.

Rename the folder to spritesMac.atlas and drag the renamed folder into your project.

In the Choose options for adding these files dialog, make sure only the SpriteKitSimpleGameMac target is ticked in the Add to targets section as shown below:

Screen Shot 2014-04-09 at 11.17.41 pm

Click Finish. Now that the folder has been imported, select sprites.atlas and turn off target membership for Macs. This ensures that each texture atlas works separately of the other.

Keeping with the spirit of staying organized, move the iOS texture atlas into the iOS group and the Mac texture atlas into the Mac group, as shown below:

Screen Shot 2014-04-09 at 11.25.38 pm

Next, go to Project\Clean. This will remove any old files from your build directory (if you forget to do this it might not work, as sprites.atlas may still exist).

Build and run your app; you should see that everything loads at the proper size, as shown below:

Screen Shot 2014-04-09 at 11.52.43 pm

At this point your app supports iPhone, iPad and Mac OS X resolutions — and Retina-compatible to boot.

Correcting Soundtrack Issues

Finally, you’ll need to deal with the missing soundtrack to your game.

Look at ViewController.m in the SpriteKitSimpleGame group. viewWillLayoutSubviews has a small section of code which instantiates AVAudioPlayer and sets it to repeat forever.

NSError *error;
NSURL *backgroundMusicURL = [[NSBundle mainBundle] URLForResource:@"background-music-aac" withExtension:@"caf"];
self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
self.backgroundMusicPlayer.numberOfLoops = -1;
[self.backgroundMusicPlayer prepareToPlay];
[self.backgroundMusicPlayer play];

Aha — you don’t have a ViewController in Mac OS X. Therefore, you’ll need to call this code from AppDelegate instead.

Find the following line in AppDelegate.m of the SpriteKitSimpleGameMac group:

@implementation AppDelegate

..and replace it with the following:

@import AVFoundation;
 
@implementation AppDelegate {
    AVAudioPlayer *backgroundMusicPlayer;
}

Next, add the following code to the top of applicationDidFinishLaunching::

NSError *error;
NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:@"background-music-aac" withExtension:@"caf"];
backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
backgroundMusicPlayer.numberOfLoops = -1;
[backgroundMusicPlayer prepareToPlay];
[backgroundMusicPlayer play];

Build and run your app; the music plays on!

You’ve resolved all of the bugs from the Mac conversion, but you still haven’t solved the issue of game controls in the Mac version of the game.

Using the Keyboard

The ninja’s movements in the iOS version of the app are controlled by tilting the device. These movements are processed by CoreMotion, and the game loop calls updatePlayerWithTimeSinceLastUpdate: to calculate the new player location for the current frame.

Responding to key presses requires a slightly different approach using the available methods to listen for keypress events.

Add the following code to updatePlayerWithTimeSinceLastUpdate: in MyScene.m just before the #endif statement:

#else
-(void)keyDown:(NSEvent *)theEvent {
 
 
}

This updates to the method to respond to a keypress as well. Note that there’s a corresponding keyUp to handle the release of the key to handle events that only last for the duration of the keypress.

You don’t want to respond to just any keypress; you can find out which key was pressed using the passed-in NSEvent.

Add the following code between the curly braces of the keyDown: method you just added:

-(void)keyDown:(NSEvent *)theEvent {
  NSString *keyPressed = [theEvent charactersIgnoringModifiers];
  if ([keyPressed length] == 1) {
      NSLog(@"Key: %c",[keyPressed characterAtIndex:0]);
  }
}

Here you extract the pressed characters from the event without any modifier keys. This means key combinations like Command + S will be ignored. As well, you check that the keypress is only one character in length to filter out any other unwanted events. You’ll dump the key pressed out to the console.

Build and run your project; press a few keys while the game is running and you’ll see the keys pressed show up in the debug area, similar to the following example:

Screen Shot 2014-04-10 at 4.54.04 pm

Since you’ll use the up and down arrow keys to move your player sprite, press those keys and see what you get in the console:

Screen Shot 2014-04-10 at 5.01.55 pm

Hmm, that looks a little odd. The arrow keys are part of the group known as function keys, so they don’t have a proper character representation. But don’t fret: there’s an easy way to detect when function keys are pressed.

NSEvent is your best friend when it comes to managing keyboard and mouse inputs on the Mac. This tutorial merely introduces NSEvent; it’s highly recommended that you check out the full NSEvent class reference.

For now, take a quick look at the section of NSEvent documentation that deals with the function keys enum. The keys you’re concerned with are NSUpArrowFunctionKey and NSDownArrowFunctionKey.

Go back to MyScene.m and find the keyDown: method you just added.

Comment out the NSLog statement and paste the following code immediately below that:

unichar charPressed = [keyPressed characterAtIndex:0];
switch (charPressed) {
    case NSUpArrowFunctionKey:
        [_player runAction:[SKAction moveByX:0.0f y:50.0f duration:0.3]];
        break;
    case NSDownArrowFunctionKey:
        [_player runAction:[SKAction moveByX:0.0f y:-50.0f duration:0.3]];
        break;
    default:
        break;
}

Here you store the pressed character as Unicode character and compare it to the up and down function keys. You then use an SKAction to move the character up and down accordingly.

Build and run your project; press the up and down arrow keys and you should see your character moving up and down the screen like so:

2014-04-10 17_33_56

You’ve spent all this time modifying the game to be played on a Mac, but you still need to check that you haven’t affected any portion of the iOS version of the game!

Build and run your project using the iOS target and play around with it a bit to make sure none of the game functions have been affected by your changes.

Where to Go From Here?

You can grab the completed sample files for this project from here.

Now that you have a good understanding of the work required to convert your iOS targeted project to a iOS / Mac targeted project, you’ll definitely want to create any new Sprite Kit games with cross-platform capabilities right from the start. I have made a cross-platform Sprite Kit project starter template on Github that might be useful – you can clone and use for your own games.

To learn more about making games that work on both iOS and OS X (especially related to scene sizes, image sizes, and aspect ratios, and UIKit vs. Cocoa Touch considerations), check out the upcoming second edition of our book iOS Games by Tutorials, where we go into more detail.

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

How to Port Your Sprite Kit Game from iOS to OS X is a post from: Ray Wenderlich

The post How to Port Your Sprite Kit Game from iOS to OS X appeared first on Ray Wenderlich.

Video Tutorial: Introduction to Unity Part 4: GameObjects

Card Game Mechanics in Sprite Kit with Swift

$
0
0
Example card image

Learn how to implement basic card game mechanics and animation.

For over 20 years, people have played Collectible Card Games (CCGs). The Wikipedia entry gives a fairly thorough recount of how these games evolved, which appear to have been inspired by role playing games like Dungeons and Dragons. Magic the Gathering is one example of a modern CCG.

At their core, CCGs are a set of custom cards representing characters, locations, abilities, events, etc. To play the game, the players must first build their own decks, then they use their individual decks to play. Most players make decks that accentuate certain factions, creatures or abilities.

In this tutorial, you’ll use Sprite Kit to manipulate images that serve as cards in a CCG app. You’ll move cards on the screen, animate them to show which cards are active, flip them over and enlarge them so you can read the text — or just admire the artwork.

If you’re new to SpriteKit, you may want to read through a beginner tutorial or indulge yourself with the iOS Games by Tutorials book. If you’re new to Swift, make sure you check out the Swift Quick Start series.

Getting Started

Since this is a card game, the best place to start is with the actual cards. Download the starter project which provides a SpriteKit project preset for an iPad in landscape mode, as well as all the images, fonts and sound files you’ll need to create a functional sample game.

Take a minute to look around the project to acquaint yourself with its file structure and content. You should see the following project folders:

  1. System: Contains the basic files to set up a SpriteKit project. This includes AppDelegate.swift, GameViewController.swift, and Main.storyboard
  2. Scenes: Contains an empty main scene GameScene.swift which will manage the game content.
  3. Card: Contains an empty Card.swift file which will manage the playing cards.
  4. Supporting Files: Contains all the images, fonts, and sound files you’ll use in the tutorial.

This game just wouldn’t be as cool without the art, so I’d like to give special thanks to Vicki from gameartguppy.com for the beautiful card artwork!

A Classy Start

Since you can’t play a card game without cards, start by making a class to represent them. Card.swift is currently a blank Swift file, so find it and add:

import Foundation
import SpriteKit
 
class Card : SKSpriteNode {
 
  required init(coder aDecoder: NSCoder!) {
    fatalError("NSCoding not supported")
  }
 
  init(imageNamed: String) {
    let cardTexture = SKTexture(imageNamed: imageNamed)
    super.init(texture: cardTexture, color: nil, size: cardTexture.size())
  }
}

You’re declaring Card as a subclass of SKSpriteNode.

To create a simple sprite with an image, you would use SKSpriteNode(imageNamed:). In order to keep this behavior, you use the inherited initializer which must call the super classes designated initializer init(texture:color:size:). You do not support NSCoding in this game.

To put sprites on the screen, open GameScene.swift and add the following code to didMoveToView():

let wolf = Card(imageNamed: "card_creature_wolf.png")
wolf.position = CGPointMake(100,200)
addChild(wolf)
 
let bear = Card(imageNamed: "card_creature_bear.png")
bear.position = CGPointMake(300, 200)
addChild(bear)

Build and run the project, and take a moment to admire the wolf and bear.

Card Images on iPad Screen

A good start…

Rule #1 for creating card games: start with creative, imaginative art. Looks like your app is shaping up nicely!

Note: Depending on screen size, you may want to zoom the simulator window, using Window\Scale\50% to fit on the screen. I also recommend using the iPad 2 simulator.

Looking at a couple of cards is fun and all, but the UI will be much cooler if you can actually move the cards. You’ll do that next!

I Want to Move It, Move It…

No matter the quality of the art, cards sitting on a screen won’t earn your app any rave reviews, because you need be able to drag them around like you can do with real paper cards. The simplest way to do this is to handle touches in the scene itself.

Still in GameScene.swift, add this new function to the class:

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
  for touch in touches {
    let location = touch.locationInNode(self)
    let touchedNode = nodeAtPoint(location)
    touchedNode.position = location
  }
}

Build and run the project, and drag those two cards around the display.

Cards move, but sometimes slide under other cards

The cards now move, but sometimes slide behind other cards. Read on to fix the problem.

As you play around with this, you’ll notice two major issues:

  1. First, since the sprites are at the same zPosition, they are arranged in the same order they are added to the scene. This means the bear card is “above” the wolf card. If you’re dragging the wolf, it appears to slide beneath the bear.
  2. Second, nodeAtPoint() returns the topmost sprite at that point. So when you drag the wolf under the bear, nodeAtPoint() returns the bear sprite and start changes its position, so you might find yourself moving the bear even though you originally moved the wolf.

While this effect is almost magical, it’s not the kind of magic you want to in the final app!

To fix this, you’ll modify the card’s zPosition while dragging. Your first inclination might be to change the zPosition of the sprite in touchesMoved, but this isn’t a good approach if you want to change it back later.

Using the beginning and ending functions is a better strategy. Still in GameScene.swift, and add the following methods:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
  for touch in touches {
    let location = touch.locationInNode(self)
    let touchedNode = nodeAtPoint(location)
    touchedNode.zPosition = 15
  }
}
 
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
  for touch in touches {
    let location = touch.locationInNode(self)
    let touchedNode = nodeAtPoint(location)
    touchedNode.zPosition = 0
  }
}

Build and run the project again, and you’ll see the cards sliding over each other as you would expect.

Cards now correctly move over each other

Cards now correctly move over each other, but looks a little plain. You’ll fix that next.

Make sure you pick a zPosition value that is greater than other cards will be. In the sample game at the end of the tutorial, there are some overlay elements at zPosition of 20. The number 19 ensures the overlay elements showed over the cards.

Now the card is moving properly over other cards, but now you need to add some satisfying depth — say, a visual indication that the card has been lifted up.

Time to make your cards dance!

Card Animations

Still in GameScene.swift, add the following to the end of the code inside the for loop of touchesBegan()

let liftUp = SKAction.scaleTo(1.2, duration: 0.2)
touchedNode.runAction(liftUp, withKey: "pickup")

and similarly in touchesEnded()

let dropDown = SKAction.scaleTo(1.0, duration: 0.2)
touchedNode.runAction(dropDown, withKey: "drop")

Here you’re using the scaleTo(scale:duration:) method of SKAction to grow the width and height of the card to 1.2x its original size when clicked and back down to 1.0 when released.

Build and run the project to see how this looks.

Moving cards with pickup and drop down animation.

This simple animation gives the appearance of picking up a card and putting it back down. Sometimes the simplest animations are the most effective.

Tinker with the scale and duration values to find what the levels that look best to you. If you set the lift and drop durations as different values you can make it appear as though that card lifts slowly, then drops quickly when released.

Wiggle, Wiggle, Wiggle

Dragging cards around now works pretty well, but you should add a bit of flair. Making the cards appear to flutter around their y-axis certainly qualifies as flair.

Since SpriteKit is a pure 2D framework, there doesn’t seem to be any way to do a partial rotation effect on a sprite. What you can do, however, is change the xScale property to give the illusion of rotation.

Again, you’ll add code to the touchesBegan() and touchesEnded() pair of functions. In touchesBegan() add the following code to the end of the for loop:

let wiggleIn = SKAction.scaleXTo(1.0, duration: 0.2)
let wiggleOut = SKAction.scaleXTo(1.2, duration: 0.2)
let wiggle = SKAction.sequence([wiggleIn, wiggleOut])
let wiggleRepeat = SKAction.repeatActionForever(wiggle)
 
touchedNode.runAction(wiggleRepeat, withKey: "wiggle")

And similarly, in touchesEnded() add:

touchedNode.removeActionForKey("wiggle")

This code makes the card appear to rotate back and forth — just a tad — as it moves around. This effect makes use of the reaction(action:, withKey:) method to add a string name to the action so that you can cancel it later.

There is a small caveat to this approach: when you remove the animation, it leaves the sprite wherever it is in the animation cycle.

You already have an action to return the card to its initial scale value of 1.0. Since scale sets both the x and y scale, that part is taken care of, but if you use another property, remember to return the initial value in the touchesEnded function.

Build and run the project, so you can see the cards now flutter when you drag them around.

Card with scaling animation to fake 3d rotation.

A simple animation to show that this card is currently active.

Challenge: In the bonus example game at the end of the tutorial, you’ll learn about using zRotation to make the cards wobble back and forth.

Try replacing the scaleXTo actions with rotateBy to replace the “wiggle” animation with a “rocking” animation. Remember to make it a cycle, which means it needs to return to its starting point before repeating.

Card rotates slightly back and forth.

Try to reproduce this effect for the wiggle animation.

Solution Inside SelectShow>

Tracking Damage

In many collectible card games, monsters like these have hit points associated with them, and can fight each other.

To implement this, you’ll need a label on top of the cards so the user can track damage inflicted on each creature. Still in GameScene.swift, add the following new method:

func newDamageLabel() -> SKLabelNode {
  let damageLabel = SKLabelNode(fontNamed: "OpenSans-Bold")
  damageLabel.name = "damageLabel"
  damageLabel.fontSize = 12
  damageLabel.fontColor = UIColor(red: 0.47, green: 0.0, blue: 0.0, alpha: 1.0)
  damageLabel.text = "0"
  damageLabel.position = CGPointMake(25, 40)
 
  return damageLabel
}

This helper method creates a new SKLabelNode which in turn will display the damage inflicted upon each card. It uses a custom font that is included in the starter project with the correct info.plist settings.

Note: For more information on installing custom fonts, check out Chapter 7 in iOS Games by Tutorials, “Labels”.

Are you wondering how the position works in this example?

Since the label is a child of the card sprite, the position is relative to the sprite’s anchor point and that is the center, by default. Usually this just takes some trial and error to get the label positioned exactly where you want it.

Use this new method to add a damage label to each card by adding the following code to the end of didMoveToView():

wolf.addChild(newDamageLabel())
bear.addChild(newDamageLabel())

Build and run the project. You should now see a red ’0′ within each card.

Card with a label for damage taken.

Cards now have a label that shows how much damage they’ve taken.

Try dragging a card, but click on the label to start dragging rather than the card itself. Notice that the label flys off somewhere — perhaps to a magical kingdom where it can rule with impunity?

No, it’s not actually that mysterious. ;]

The problem here is that when you call nodeAtPoint it returns the topmost SKNode of any type, which in this case is the SKLabelNode. When you then change the node’s position, the label moves instead of the card. Ahhh…yes, there is a logical explanation.

Dragging on top of the damage label causes problems.

The result of starting a touch on top of the damage label. Oops. (Changed the background to white to make the label more visible)

Pros and Cons Of Scene Touch Handling

Before going any further, let’s stop for a moment and muse upon some of the advantages and disadvantages of handling the touches at the scene level.

Touch handling at the scene level is a good place to start working with a project because it’s the simplest, easiest approach. In fact, if your sprites have transparent regions that should be ignored, such as hex grids, this may be the only reasonable solution.

However, it starts to fall apart when you have composite sprites. For example, these could contain multiple images, labels or even a health bar. It can also be unwieldy and complicated if you have different rules for different sprites.

One gotcha that comes into play when you use nodeAtPoint is that it always returns a node.

What if you drag outside of one of the card sprites? Because SKScene is a subclass of SKNode, if the touch location intersects no other node, the scene itself returns as a SKNode.

When you changed the position and did animations before, you may not have known but you really should’ve checked to see that touchedNode was not the scene itself, but it’s okay because this is a learning experience.

You’ll be happy to know there is a better solution…

Handle Those Touches! Handle Them!

What can you do instead? Well, you can make the Card class responsible for handling its own touches. The logistics of this approach are fairly straightforward. Open Card.swift and add the following to init(imageNamed:):

userInteractionEnabled = true

This allows the Card class to intercept touches as opposed to passing them through to the scene. SpriteKit will send touch events to the topmost instance with this property set.

Next, you remove the three touch handler functions,touchesBegan(), touchesMoved(), and touchesEnded() from GameScene.swift and add them to Card.swift.

The original code won’t work exactly as-is, so it needs some changes to work within the node.

As a challenge, see if you can make the appropriate changes without checking the spoiler!

Hint: Since touch events are sent directly to the correct sprite, you don’t have to figure out which sprite to modify.

Solution Inside SelectShow>

Build and run the project, and you’ll notice that this fixes the earlier issue of the flying label.

Card can be moved the same as before

Card can be moved the same as before.

Two Sides of the Story

Now take a moment to observe how the Card nodes initialize. Currently, you’re simply using the string image name to create a texture, and sending that to the superclass initializer.

In order to add attributes like attack and defense values, or mystical spell effects, you need to setup properties and configure them based on the specific card’s data. Instead of using strings to identify cards, which are prone to typos, you can define an enumeration instead. Open Card.swift and add the following between the import lines and the class definition:

enum CardName: Int {
    case CreatureWolf = 0,
    CreatureBear,       // 1
    CreatureDragon,     // 2
    Energy,             // 3
    SpellDeathRay,      // 4
    SpellRabid,         // 5
    SpellSleep,         // 6
    SpellStoneskin      // 7
}

This defines CardName as a new type that you can use to identify individual cards. The integer values will be helpful as a reference when working with the cards in a deck.

Next, you need to define some custom properties for the Card class. Add this between the class declaration line and init

let frontTexture: SKTexture
let backTexture: SKTexture
var largeTexture: SKTexture?
let largeTextureFilename: String

Replace init(imageNamed:) in Card.swift with

init(cardNamed: CardName) {
 
  // initialize properties
  backTexture = SKTexture(imageNamed: "card_back.png")
 
  switch cardNamed {
  case .CreatureWolf:
    frontTexture = SKTexture(imageNamed: "card_creature_wolf.png")
    largeTextureFilename = "card_creature_wolf_large.png"
 
  case .CreatureBear:
    frontTexture = SKTexture(imageNamed: "card_creature_bear.png")
    largeTextureFilename = "Card_creature_bear_large.png"
 
  default:
    frontTexture = SKTexture(imageNamed: "card_back.png")
    largeTextureFilename = "card_back_large.png"
  }
 
  // call designated initializer on super
  super.init(texture: frontTexture, color: nil, size: frontTexture.size())
 
 
  // set properties defined in super
  userInteractionEnabled = true
}

Finally, open GameScene.swift and change didMoveToView() to use the new enum instead of the string filename:

let wolf = Card(cardNamed: .CreatureWolf)
wolf.position = CGPointMake(100,200)
addChild(wolf)
 
let bear = Card(cardNamed: .CreatureBear)
bear.position = CGPointMake(300, 200)
addChild(bear)

There are several changes here:

  • First, you add a new type called CardName, the advantage of an enum like this is that the compiler knows all the possible values and it will warn you if you mistype one of the names. Additionally, Xcode autocomplete should be able to help as you type the first few characters of the name.
  • Next, you create four new properties in Card.swift to store the values of each SKTexture, which will be utilized based on the state of the card. Each card requires a front image, back image and large front image. largeTextureFilename is there to save memory by preventing a large image from loading until it’s actually needed.
  • Next you update the init method to take a CardName rather than a String and set each of the newly created properties based on the type of Card. This makes use of the new-and-improved switch statement in Swift. Here, switch cases do not automatically fall through. Additionally, you must either provide a default case, or cover all possible values. Once you have custom properties, such as attack and defense, you can assign those values inside the switch statement.

    There is a specific order you have to follow when initializing swift objects.

    • First, make sure all of the properties defined in the class have default values.
    • Second, call a designated initializer for the superclass.
    • Third, set any properties defined in the superclass, and call any functions you need to on the object.
  • Lastly, you update the code in GameScene.swift to use the new init method of Card.

Build and run the project, and make sure that everything works just as it did before.

Note: Since you’re just working with seven cards, there’s no need for anything more complicated to initialize cards. This particular strategy probably won’t work very well if you have 10′s or 100′s of cards. In that case, you’ll a system to store all of the card attributes in a configuration file, like a .json file. You’ll also want to design the init system to pull out the relevant part of the configuration as a dictionary and build the card data from that.

Challenge:
Finish Card by adding the correct images for the other cards, such as the fierce dragon. You’ll find the images in the cards folder inside Supporting Files.

Image of Dragon creature card

Dun Dun Dun

Flip Flop

Finally, add some card-like actions to make the game more realistic. Since the basic premise is that two players will share an iPad, the cards need to be able to turn face down so the other player cannot see them.

An easy way to do this is to make the card flip over when you double tap it. However, you need a property to keep track of the card state to make this possible.

Open Card.swift and add the following property below the other properties:

var faceUp = true

Next, add a function to swap the textures that will make a card appears flipped:

func flip() {
  if faceUp {
    self.texture = self.backTexture
    if let damageLabel = self.childNodeWithName("damageLabel") {
      damageLabel.hidden = true
    }
    self.faceUp = false
  } else {
    self.texture = self.frontTexture
    if let damageLabel = self.childNodeWithName("damageLabel") {
      damageLabel.hidden = false
    }
    self.faceUp = true
  }
}

Finally, add the following to the beginning of touchesBegan, just inside the for-in loop.

if touch.tapCount > 1 {
  flip()
}

Now you understand why you saved the front and back card images as textures earlier — it makes flipping the cards delightfully easy. You also hide damageLabel so the number is not shown when the card is face down.

Build and run the project and flip those cards.

Card flip

Simple card flip by swapping out the texture. The little bounce is the pick-up animation triggered by the first touch.

Note: At this point, it’s ideal for the damage label to be a property that initializes during Card initialization. For the sake of keeping this tutorial simple and straightforward, it is in here as a child node. Try pulling it from GameScene and putting it into Card for a little extra credit.

The effect is ok, but you can do better. One trick is to use the scaleToX animation to make it look as though it actually flips.

Replace flip with:

func flip() {
  let firstHalfFlip = SKAction.scaleXTo(0.0, duration: 0.4)
  let secondHalfFlip = SKAction.scaleXTo(1.0, duration: 0.4)
 
  setScale(1.0)
 
  if faceUp {
    runAction(firstHalfFlip) {
      self.texture = self.backTexture
      if let damageLabel = self.childNodeWithName("damageLabel") {
        damageLabel.hidden = true
      }
      self.faceUp = false
      self.runAction(secondHalfFlip)
    }
  } else {
    runAction(firstHalfFlip) {
      self.texture = self.frontTexture
      if let damageLabel = self.childNodeWithName("damageLabel") {
        damageLabel.hidden = false
      }
      self.faceUp = true
      self.runAction(secondHalfFlip)
    }
  }
}

The scaleXTo action shrinks only the horizontal direction and gives it a pretty cool 2D flip animation. The animation splits into two halves so that you can swap the texture halfway. The setScale function makes sure the other scale animations don’t get in the way.

Build and run the project to see the new “flip” effect in action.

Card flip with animation

Now you have a nice looking flip animation.

Things are looking great, but you can’t fully appreciate the bear’s goofy grin when the cards are so small. If only you could enlarge a selected card to see its details…

Big Time

The last effect you’ll work with in this tutorial is modifying the double tap action so that it enlarges the card. Add these two properties to the beginning of Card.swift with the other properties:

var enlarged = false
var savedPosition = CGPointZero

Add the following method to perform the enlarge action:

func enlarge() {
  if enlarged {
    enlarged = false
    zPosition = 0
    position = savedPosition
    setScale(1.0)
  } else {
    enlarged = true
    savedPosition = position
    zPosition = 20
    position = CGPointMake(CGRectGetMidX(parent.frame), CGRectGetMidY(parent.frame))
    removeAllActions()
    setScale(5.0)
  }
}

Remember to update touchesBegan() to call the new function, instead of flip()

if touch.tapCount > 1 {
  enlarge()
}
 
if enlarged { return }

Finally, make a small update to touchesMoved() and touchesEnded by adding the following line to each before the for-in loop:

if enlarged { return }

You need to add the extra property savedPosition so the card can be moved back to its original position. This is the point when touch-handling logic becomes a bit tricky, as mentioned earlier.

The tapCount check at the beginning of the function prevents glitches when the card is enlarged and then tapped again. Without the early return, the large image would shrink and start the wiggle animation.

It also doesn’t make sense to move the enlarged image, and there is nothing to do when the touch ends, so both functions return early when the card is enlarged.

Build and run the app to see the card grow and grow to fill the screen.

Basic card enlarging.

Basic card enlarging. Would look much better with some animation, and the enlarged image is fuzzy.

But why is it all pixelated? Vicki’s artwork is much too nice to place under such duress. You’re enlarging this way because you’re not using the large versions of the images in the cards_large folder inside Supporting Files.

Because loading the large images for all the cards at the beginning can waste memory, it’s best to make it so they don’t load until the user needs them.

The final version of the enlarge function is as follows:

func enlarge() {
  if enlarged {
    let slide = SKAction.moveTo(savedPosition, duration:0.3)
    let scaleDown = SKAction.scaleTo(1.0, duration:0.3)
    runAction(SKAction.group([slide, scaleDown])) {
      self.enlarged = false
      self.zPosition = 0
    }
  } else {
    enlarged = true
    savedPosition = position
 
    if largeTexture != nil {
      texture = largeTexture
    } else {
      largeTexture = SKTexture(imageNamed: largeTextureFilename)
      texture = largeTexture
    }
 
    zPosition = 20
 
    let newPosition = CGPointMake(CGRectGetMidX(parent.frame), CGRectGetMidY(parent.frame))
    removeAllActions()
 
    let slide = SKAction.moveTo(newPosition, duration:0.3)
    let scaleUp = SKAction.scaleTo(5.0, duration:0.3)
    runAction(SKAction.group([slide, scaleUp]))
  }
}

The animations are fairly straightforward at this point.

The card’s position saves before running an animation, so it returns to its original position. To prevent the pickup and drop animations from interfering with the animation as it scales up, you add the removeAllActions() function.

When the scale down animations run, the enlarged and zPosition properties don’t set until the animation completes. If these values change earlier, an enlarged card sitting behind another card will appear to slide underneath as it returns to its previous position.

Since largeTexture is defined as an optional, it can have a value of nil, or “no value”. The if statement tests to see if it has a value, and loads the texture if it doesn’t.

Note: Optionals are a core part of learning Swift, especially since it works differently than nil values in Objective-C.

Build and run the app once again. You should now see a nice, smooth animation from the card’s initial position to the final enlarged position. You’ll also see the cards in full, clean, unpixelated splendor.

Card enlargement with animation.

Animating the card enlargement, and swapping to the large image make this look much nicer.

Final Challenge: Sound effects are an important part of any game, and there are some sound files included in the starter project. See if you can use SKAction.playSoundFileNamed(soundFile:, waitForCompletion:) to add a sound effect to the card flip, and the enlarge action.

Where to Go from Here?

The final project for this tutorial can be found here.

At this point, you understand the basic — and some not so basic — card mechanics that you can put to use in your own card game.

This sample project has many subtle animations that you can tweak, so make sure you play around with the different values to find what you like and what works for you.

Once you’re happy with the animations, there are board regions, decks, attacks and many other features that are simply too much to address in a single article like this. Take a look at the completed example game in Objective-C and Swift to learn more about the other elements that go into game development.

Use the forum below to comment, ask questions or share your ideas for animating cards with Swift. Thanks for taking the time to work through this tutorial!

Card Game Mechanics in Sprite Kit with Swift is a post from: Ray Wenderlich

The post Card Game Mechanics in Sprite Kit with Swift appeared first on Ray Wenderlich.

How To Make a Custom Control in Swift

$
0
0
Learn how to make a custom control like a slider that has two thumbs

Learn how to make a custom control like a slider that has two thumbs

Update note: This tutorial was updated for iOS 8 and Swift by Mikael Konutgan. Original post by Tutorial Team member Colin Eberhardt.

User interface controls are one of the most important building blocks of any application. They serve as the graphical components that allow your users to view and interact with your application. Apple supplies a set of controls, such as UITextField, UIButton and UISwitch. Armed with this toolbox of pre-existing controls, you can create a great variety of user interfaces.

However, sometimes you need to do something just a little bit different; something that the stock controls can’t handle quite the way you want.

Custom controls are nothing more than controls that you have created yourself; that is, controls that do not come with the UIKit framework. Custom controls, just like standard controls, should be generic and versatile. As a result, you’ll find there is an active and vibrant community of developers who love to share their custom control creations.

In this tutorial, you will implement your very own RangeSlider custom control. This control is like a double-ended slider, where you can pick both a minimum and maximum value. You’ll touch on such concepts as extending existing controls, designing and implementing your control’s API, and even how to share your new control with the development community.

Time to start customizing!

Note: At the time of writing this tutorial, our understanding is we cannot post screenshots of iOS 8 since it is still in beta. All the screenshots here are from previous versions of iOS, but your results will look very similar.

Getting Started

Say you’re developing an application for searching property-for-sale listings. This fictional application allows the user to filter search results so they fall within a certain price range.

You could provide an interface which presents the user with a pair of UISlider controls, one for setting the maximum price and one for setting the minimum price. However, this interface doesn’t really help the user visualize the price range. It would be much better to present a single slider with two thumbs to indicate the high and low price range they are searching for.

You could build this range slider by subclassing UIView and creating a bespoke view for visualizing price ranges. That would be fine for the context of your app — but it would be a struggle to port it to other apps.

It’s a much better idea to make this new component as generic as possible, so that you can reuse it in any context where it’s appropriate. This is the very essence of custom controls.

Fire up Xcode. Go to File/New/Project, select the iOS/Application/Single View Application template and click Next. On the next screen, enter CustomSliderExample as the product name, choose your desired Organization Name and Organization Identifier, then make sure that Swift is selected as the language, iPhone is selected as the Device and that Use Core Data is not selected.

Finally choose a place to save the project and click Create.

The first decision you need to make when creating a custom control is which existing class to subclass, or extend, in order to make your new control.

Your class must be a UIView subclass in order for it be available in the application’s UI.

If you check Apple’s UIKit reference, you’ll see that a number of the framework controls such as UILabel and UIWebView subclass UIView directly. However, there are a handful, such as UIButton and UISwitch which subclass UIControl, as shown in the hierarchy below:

Note: For a complete class hierarchy of UI components, check out the UIKit Framework Reference.

UIControl implements the target-action pattern, which is a mechanism for notifying subscribers of changes. UIControl also has a few properties that relate to control state. You’ll be using the target-action pattern in this custom control, so UIControl will serve as a great starting point.

Right-click the CustomSliderExample group in the Project Navigator and select New File…, then select the iOS/Source/Cocoa Touch Class template and click Next. Call the class RangeSlider, enter UIControl into the Subclass of field and make sure the language is Swift. Click Next and then Create to choose the default location to store the file associated with this new class.

Although writing code is pretty awesome, you probably want to see your control rendered on the screen to measure your progress! Before you write any code for your control, you should add your control to the view controller so that you can watch the evolution of the control.

Open up ViewController.swift and replace its contents with the following:

import UIKit
 
class ViewController: UIViewController {
    let rangeSlider = RangeSlider(frame: CGRectZero)
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        rangeSlider.backgroundColor = UIColor.redColor()
        view.addSubview(rangeSlider)
    }
 
    override func viewDidLayoutSubviews() {
        let margin: CGFloat = 20.0
        let width = view.bounds.width - 2.0 * margin
        rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length,
            width: width, height: 31.0)
    }
}

The above code simply creates an instance of your all-new control in the given frame and adds it to the view. The control background color has been set to red so that it will be visible against the app’s background. If you didn’t set the control’s background to red, then the control would be clear — and you’d be wondering where your control went! :]

Build and run your app; you should see something very similar to the following:

Before you add the visual elements to your control, you’ll need a few properties to keep track of the various pieces of information that are stored in your control. This will form the start of your control’s Application Programming Interface, or API for short.

Note: Your control’s API defines the methods and properties that you decide to expose to the other developers who will be using your control. You’ll read about API design a little later in this article — for now, just hang tight!

Adding Default Control Properties

Open up RangeSlider.swift and replace the code with the following:

import UIKit
 
class RangeSlider: UIControl {
    var minimumValue = 0.0
    var maximumValue = 1.0
    var lowerValue = 0.2
    var upperValue = 0.8
}

These four properties are all you need to describe the state of this control, providing maximum and minimum values for the range, along with the upper and lower values set by the user.

Well-designed controls should define some default property values, or else your control will look a little strange when it draws on the screen! You did that above as well.

Now it’s time to work on the interactive elements of your control; namely, the thumbs to represent the high and low values, and the track the thumbs slide on.

Images vs. CoreGraphics

There are two main ways that you can render controls on-screen:

  1. Images – create images that represent the various elements of your control
  2. CoreGraphics – render your control using a combination of layers and CoreGraphics

There are pros and cons to each technique, as outlined below:

Images — constructing your control using images is probably the simplest option in terms of authoring the control — as long as you know how to draw! :] If you want your fellow developers to be able to change the look and feel of your control, you would typically expose these images as UIImage properties.

Using images provides the most flexibility to developers who will use your control. Developers can change every single pixel and every detail of your control’s appearance, but this requires good graphic design skills — and it’s difficult to modify the control from code.

Core Graphics — constructing your control using Core Graphics means that you have to write the rendering code yourself, which will require a bit more effort. However, this technique allows you to create a more flexible API.

Using Core Graphics, you can parameterize every feature of your control, such as colors, border thickness, and curvature — pretty much every visual element that goes into drawing your control! This approach allows developers who use your control to fully tailor it to suit their needs.

In this tutorial you’ll use the second technique — rendering the control using Core Graphics.

Note: Interestingly, Apple tend to opt for using images in their controls. This is most likely because they know the size of each control and don’t tend to want to allow too much customization. After all, they want all apps to end up with a similar look-and-feel.

Open up RangeSlider.swift and add the following import to the top of the file, just below import UIKit:

import QuartzCore

Add the following properties to RangeSlider, just after the ones we defined above:

let trackLayer = CALayer()
let lowerThumbLayer = CALayer()
let upperThumbLayer = CALayer()
 
var thumbWidth: CGFloat {
    return CGFloat(bounds.height)
}

These three layers — trackLayer, lowerThumbLayer, and upperThumbLayer — will be used to render the various components of your slider control. thumbWidth will be used for layout purposes.

Next up are some default graphical properties of the control itself.

Inside the RangeSlider class, add an initializer and the following helper functions:

init(frame: CGRect) {
    super.init(frame: frame)
 
    trackLayer.backgroundColor = UIColor.blueColor().CGColor
    layer.addSublayer(trackLayer)
 
    lowerThumbLayer.backgroundColor = UIColor.greenColor().CGColor
    layer.addSublayer(lowerThumbLayer)
 
    upperThumbLayer.backgroundColor = UIColor.greenColor().CGColor
    layer.addSublayer(upperThumbLayer)
 
    updateLayerFrames()
}
 
func updateLayerFrames() {
    trackLayer.frame = bounds.rectByInsetting(dx: 0.0, dy: bounds.height / 3)
    trackLayer.setNeedsDisplay()
 
    let lowerThumbCenter = CGFloat(positionForValue(lowerValue))
 
    lowerThumbLayer.frame = CGRect(x: lowerThumbCenter - thumbWidth / 2.0, y: 0.0,
      width: thumbWidth, height: thumbWidth)
    lowerThumbLayer.setNeedsDisplay()
 
    let upperThumbCenter = CGFloat(positionForValue(upperValue))
    upperThumbLayer.frame = CGRect(x: upperThumbCenter - thumbWidth / 2.0, y: 0.0,
        width: thumbWidth, height: thumbWidth)
    upperThumbLayer.setNeedsDisplay()
}
 
func positionForValue(value: Double) -> Double {
    let widthDouble = Double(thumbWidth)
    return Double(bounds.width - thumbWidth) * (value - minimumValue) /
        (maximumValue - minimumValue) + Double(thumbWidth / 2.0)
}

The initializer simply creates three layers and adds them as children of the control’s root layer, then updateLayerFrames, well, updates the layer frames to fit! :]

Finally, positionForValue maps a value to a location on screen using a simple ratio to scale the position between the minimum and maximum range of the control.

Next, override frame, and implement a property observer by adding the following to RangeSlider.swift:

override var frame: CGRect {
    didSet {
        updateLayerFrames()
    }
}

The property observer updates the layer frames when the frame changes. This is necessary when the control is initialized with a frame that is not its final frame like in ViewController.swift.

Build and run your app; your slider is starting to take shape! It should look similar to the screenshot below:

Remember, red is the background of the entire control. Blue is the “track” for the slider and green will be the two thumb points for the upper and lower values.

Your control is starting to take shape visually, but almost every control provides a way for the app user to interact with it.

For your control, the user must be able to drag each thumb to set the desired range of the control. You’ll handle those interactions, and update both the UI and the properties exposed by the control.

Adding Interactive Logic

The interaction logic needs to store which thumb is being dragged, and reflect that in the UI. The control’s layers are a great place to put this logic.

As before, create a new Cocoa Touch Class in Xcode named RangeSliderThumbLayer which is a subclass of CALayer.

Replace the contents of the newly added RangeSliderThumbLayer.swift with the following:

import UIKit
import QuartzCore
 
class RangeSliderThumbLayer: CALayer {
    var highlighted = false
    weak var rangeSlider: RangeSlider?
}

This simply adds two properties: one that indicates whether this thumb is highlighted, and one that is a reference back to the parent range slider. Since the RangeSlider owns the two thumb layers, the back reference is a weak variable to avoid a retain cycle.

Open RangeSlider.swift and change the type of the lowerThumbLayer and upperThumbLayer properties, by replacing their definitions with the following:

let lowerThumbLayer = RangeSliderThumbLayer()
let upperThumbLayer = RangeSliderThumbLayer()

Still in RangeSlider.swift, find init and add the following lines to it:

lowerThumbLayer.rangeSlider = self
upperThumbLayer.rangeSlider = self

The above code simply sets the layer’s rangeSlider property to populate the back reference to self.

Build and run your project; check to see if everything still looks the same.

Now that you have the slider’s thumbs layers in place using RangeSliderThumbLayer, you need to add the ability for the user to drag the slider thumbs around.

Adding Touch Handlers

Open RangeSlider.swift add the following property along with the others:

var previousLocation = CGPoint()

This property will be used to track the touch locations.

How are you going to track the various touch and release events of your control?

UIControl provides several methods for tracking touches. Subclasses of UIControl can override these methods in order to add their own interaction logic.

In your custom control, you will override three key methods of UIControl: beginTrackingWithTouch, continueTrackingWithTouch and endTrackingWithTouch.

Add the following method to RangeSlider.swift:

override func beginTrackingWithTouch(touch: UITouch!, withEvent event: UIEvent!) -> Bool {
    previousLocation = touch.locationInView(self)
 
    // Hit test the thumb layers
    if lowerThumbLayer.frame.contains(previousLocation) {
        lowerThumbLayer.highlighted = true
    } else if upperThumbLayer.frame.contains(previousLocation) {
        upperThumbLayer.highlighted = true
    }
 
    return lowerThumbLayer.highlighted || upperThumbLayer.highlighted
}

The method above is invoked when the user first touches the control.

First, it translates the touch event into the control’s coordinate space. Next, it checks each thumb layer to see whether the touch was within its frame. The return value for the above method informs the UIControl superclass whether subsequent touches should be tracked.

Tracking touch events continues if either thumb is highlighted.

Now that you have the initial touch event, you’ll need to handle the events as the user moves their finger across the screen.

Add the following methods to RangeSlider.swift:

func boundValue(value: Double, toLowerValue lowerValue: Double, upperValue: Double) -> Double {
    return min(max(value, lowerValue), upperValue)
}
 
override func continueTrackingWithTouch(touch: UITouch!, withEvent event: UIEvent!) -> Bool {
    let location = touch.locationInView(self)
 
    // 1. Determine by how much the user has dragged
    let deltaLocation = Double(location.x - previousLocation.x)
	let deltaValue = (maximumValue - minimumValue) * deltaLocation / Double(bounds.width - bounds.height)
 
    previousLocation = location
 
    // 2. Update the values
    if lowerThumbLayer.highlighted {
        lowerValue += deltaValue
        lowerValue = boundValue(lowerValue, toLowerValue: minimumValue, upperValue: upperValue)
    } else if upperThumbLayer.highlighted {
        upperValue += deltaValue
        upperValue = boundValue(upperValue, toLowerValue: lowerValue, upperValue: maximumValue)
    }
 
    // 3. Update the UI
    CATransaction.begin()
    CATransaction.setDisableActions(true)
 
    updateLayerFrames()
 
    CATransaction.commit()
 
    return true
}

boundValue will clamp the passed in value so it is within the specified range. Using this helper function is a little easier to read than a nested min / max call.

Here’s a breakdown of continueTrackingWithTouch, comment by comment:

  1. First you calculate a delta location, which determines the number of pixels the user’s finger travelled. You then convert it into a scaled delta value based on the minimum and maximum values of the control.
  2. Here you adjust the upper or lower values based on where the user drags the slider to.
  3. This section sets the disabledActions flag inside a CATransaction. This ensures that the changes to the frame for each layer are applied immediately, and not animated. Finally, updateLayerFrames is called to move the thumbs to the correct location.

You’ve coded the dragging of the slider — but you still need to handle the end of the touch and drag events.

Add the following method to RangeSlider.swift:

override func endTrackingWithTouch(touch: UITouch!, withEvent event: UIEvent!) {
    lowerThumbLayer.highlighted = false
    upperThumbLayer.highlighted = false
}

The above code simply resets both thumbs to a non-highlighted state.

Build and run your project, and play around with your shiny new slider! You should be able to drag the green thumb points around.

Screenshot #2

You’ll notice that when the slider is tracking touches, you can drag your finger beyond the bounds of the control, then back within the control without losing your tracking action. This is an important usability feature for small screen devices with low precision pointing devices — or as they’re more commonly known, fingers! :]

Change Notifications

You now have an interactive control that the user can manipulate to set upper and lower bounds. But how do you communicate these change notifications to the calling app so that the app knows the control has new values?

There are a number of different patterns that you could implement to provide change notification: NSNotification, Key-Value-Observing (KVO), the delegate pattern, the target-action pattern and many others. There are so many choices!

What to do?

If you look at the UIKit controls, you’ll find they don’t use NSNotification or encourage the use of KVO, so for consistency with UIKit you can exclude those two options. The other two patterns — delegates and target-action patterns — are used extensively in UIKit.

Here’s a detailed analysis of the delegate and the target-action pattern:

Delegate pattern – With the delegate pattern you provide a protocol which contains a number of methods that are used for a range of notifications. The control has a property, usually named delegate, which accepts any class that implements this protocol. A classic example of this is UITableView which provides the UITableViewDelegate protocol. Note that controls only accept a single delegate instance. A delegate method can take any number of parameters, so you can pass in as much information as you desire to such methods.

Target-action pattern – The target-action pattern is provided by the UIControl base class. When a change in control state occurs, the target is notified of the action which is described by one of the UIControlEvents enum values. You can provide multiple targets to control actions and while it is possible to create custom events (see UIControlEventApplicationReserved) the number of custom events is limited to 4. Control actions do not have the ability to send any information with the event. So they cannot be used to pass extra information when the event is fired.

The key differences between the two patterns are as follows:

  • Multicast — the target-action pattern multicasts its change notifications, while the delegate pattern is bound to a single delegate instance.
  • Flexibility — you define the protocols yourself in the delegate pattern, meaning you can control exactly how much information you pass. Target-action provides no way to pass extra information and clients would have to look it up themselves after receiving the event.

Your range slider control doesn’t have a large number of state changes or interactions that you need to provide notifications for. The only things that really change are the upper and lower values of the control.

In this situation, the target-action pattern makes perfect sense. This is one of the reasons why you were told to subclass UIControl right back at the start of this tutorial!

Aha! It’s making sense now! :]

The slider values are updated inside continueTrackingWithTouch:withEvent:, so this is where you’ll need to add your notification code.

Open up RangeSlider.swift, locate continueTrackingWithTouch, and add the following just before the “return true” statement:

sendActionsForControlEvents(.ValueChanged)

That’s all you need to do in order to notify any subscribed targets of the changes!

Now that you have your notification handling in place, you should hook it up to your app.

Open up ViewController.swift and add the following code to the end of viewDidLoad:

rangeSlider.addTarget(self, action: "rangeSliderValueChanged:", forControlEvents: .ValueChanged)

The above code invokes the rangeSliderValueChanged method each time the range slider sends the UIControlEventValueChanged action.

Now add the following method to ViewController.swift:

func rangeSliderValueChanged(rangeSlider: RangeSlider) {
    println("Range slider value changed: (\(rangeSlider.lowerValue) \(rangeSlider.upperValue))")
}

This method simply logs the range slider values to the console as proof that your control is sending notifications as planned.

Build and run your app, and move the sliders back and forth. You should see the control’s values in the console, as in the screenshot below:

Range slider value changed: (0.117670682730924 0.390361445783134)
Range slider value changed: (0.117670682730924 0.38835341365462)
Range slider value changed: (0.117670682730924 0.382329317269078)
Range slider value changed: (0.117670682730924 0.380321285140564)
Range slider value changed: (0.119678714859438 0.380321285140564)
Range slider value changed: (0.121686746987952 0.380321285140564)

You’re probably sick of looking at the multi-colored range slider UI by now. It looks like an angry fruit salad!

It’s time to give the control a much-needed facelift!

Modifying Your Control With Core Graphics

First, you’ll update the graphics of the “track” that the slider thumbs move along.

Add another subclass of CALayer to the project just like before, this time calling it RangeSliderTrackLayer.

Open up the newly added file RangeSliderTrackLayer.swift, and replace its contents with the following:

import UIKit
import QuartzCore
 
class RangeSliderTrackLayer: CALayer {
    weak var rangeSlider: RangeSlider?
}

The code above adds a reference back to the range slider, just as you did previously for the thumb layer.

Open up RangeSlider.swift, locate the trackLayer property and modify it to be an instance of the new layer class, as below:

let trackLayer = RangeSliderTrackLayer()

Now find init and replace the method with the following:

init(frame: CGRect) {
    super.init(frame: frame)
 
    trackLayer.rangeSlider = self
    trackLayer.contentsScale = UIScreen.mainScreen().scale
    layer.addSublayer(trackLayer)
 
    lowerThumbLayer.rangeSlider = self
    lowerThumbLayer.contentsScale = UIScreen.mainScreen().scale
    layer.addSublayer(lowerThumbLayer)
 
    upperThumbLayer.rangeSlider = self
    upperThumbLayer.contentsScale = UIScreen.mainScreen().scale
    layer.addSublayer(upperThumbLayer)
}

The code above ensures that the new track layer has a reference to the range slider — and that the hideous background colors are no longer applied. :] Setting the contentsScale factor to match that of the device’s screen will ensure everything is crisp on retina displays.

There’s just one more bit — remove the red background of the control.

Open up ViewController.swift, locate the following line in viewDidLoad and remove it:

rangeSlider.backgroundColor = UIColor.redColor()

Build and run now…what do you see?

Screenshot #3

Do you see nothing? That’s good!

Good? What’s good about that? All of your hard work — gone?!?!

Don’t fret — you’ve just removed the gaudy test colors that were applied to the layers. Your controls are still there — but now you have a blank canvas to dress up your controls!

Since most developers like it when controls can be configured to emulate the look and feel of the particular app they are coding, you will add some properties to the slider to allow customization of the “look” of the control.

Open RangeSlider.swift and add the following properties just beneath the ones you added earlier:

var trackTintColor = UIColor(white: 0.9, alpha: 1.0)
var trackHighlightTintColor = UIColor(red: 0.0, green: 0.45, blue: 0.94, alpha: 1.0)
var thumbTintColor = UIColor.whiteColor()
 
var curvaceousness : CGFloat = 1.0

The purposes of the various color properties are fairly straightforward. And curvaceousness? Well, that one is in there for a bit of fun — you’ll find out what it does shortly! :]

Next, open up RangeSliderTrackLayer.swift.

This layer renders the track that the two thumbs slide on. It currently inherits from CALayer, which only renders a solid color.

In order to draw the track, you need to implement drawInContext: and use the Core Graphics APIs to perform the rendering.

Note: To learn about Core Graphics in depth, the Core Graphics 101 tutorial series from this site is highly recommended reading, as exploring Core Graphics is out of scope for this tutorial.

Add the following method to RangeSliderTrackLayer:

override func drawInContext(ctx: CGContext!) {
    if let slider = rangeSlider {
        // Clip
        let cornerRadius = bounds.height * slider.curvaceousness / 2.0
        let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
        CGContextAddPath(ctx, path.CGPath)
 
        // Fill the track
        CGContextSetFillColorWithColor(ctx, slider.trackTintColor.CGColor)
        CGContextAddPath(ctx, path.CGPath)
        CGContextFillPath(ctx)
 
        // Fill the highlighted range
        CGContextSetFillColorWithColor(ctx, slider.trackHighlightTintColor.CGColor)
        let lowerValuePosition = CGFloat(slider.positionForValue(slider.lowerValue))
        let upperValuePosition = CGFloat(slider.positionForValue(slider.upperValue))
        let rect = CGRect(x: lowerValuePosition, y: 0.0, width: upperValuePosition - lowerValuePosition, height: bounds.height)
        CGContextFillRect(ctx, rect)
    }
}

Once the track shape is clipped, the background is filled in. After that, the highlighted range is filled in.

Build and run to see your new track layer rendered in all its glory! It should look like the following:

Screenshot #4

Play around with the various values for the exposed properties to see how they affect the rendering of the control.

If you’re still wondering what curvaceousness does, try changing that as well!

You’ll use a similar approach to draw the thumb layers.

Open up RangeSliderThumbLayer.swift and add the following method just below the property declarations:

override func drawInContext(ctx: CGContext!) {
    if let slider = rangeSlider {
        let thumbFrame = bounds.rectByInsetting(dx: 2.0, dy: 2.0)
        let cornerRadius = thumbFrame.height * slider.curvaceousness / 2.0
        let thumbPath = UIBezierPath(roundedRect: thumbFrame, cornerRadius: cornerRadius)
 
        // Fill - with a subtle shadow
        let shadowColor = UIColor.grayColor()
        CGContextSetShadowWithColor(ctx, CGSize(width: 0.0, height: 1.0), 1.0, shadowColor.CGColor)
        CGContextSetFillColorWithColor(ctx, slider.thumbTintColor.CGColor)
        CGContextAddPath(ctx, thumbPath.CGPath)
        CGContextFillPath(ctx)
 
        // Outline
        CGContextSetStrokeColorWithColor(ctx, shadowColor.CGColor)
        CGContextSetLineWidth(ctx, 0.5)
        CGContextAddPath(ctx, thumbPath.CGPath)
        CGContextStrokePath(ctx)
 
        if highlighted {
            CGContextSetFillColorWithColor(ctx, UIColor(white: 0.0, alpha: 0.1).CGColor)
            CGContextAddPath(ctx, thumbPath.CGPath)
            CGContextFillPath(ctx)
        }
    }
}

Once a path is defined for the shape of the thumb, the shape is filled in. Notice the subtle shadow which gives the impression the thumb hovers above the track. The border is rendered next. Finally, if the thumb is highlighted — that is, if it’s being moved — a subtle grey shading is applied.

One last thing before we build and run. Change the declaration of the highlighted property as follows:

var highlighted: Bool = false {
    didSet {
        setNeedsDisplay()
    }
}

Here, you define a property observer so that the layer is redrawn every time the highlighted property changes. That will change the fill color slightly for when the touch event is active.

Build and run once again; it’s looking pretty sharp and should resemble the screenshot below:

Screenshot #5

You can easily see that rendering your control using Core Graphics is really worth the extra effort. Using Core Graphics results in a much more versatile control compared to one that is rendered from images alone.

Handling Changes to Control Properties

So what’s left? The control now looks pretty snazzy, the visual styling is versatile, and it supports target-action notifications.

It sounds like you’re done — or are you?

Think for a moment about what happens if one of the range slider properties is set in code after it has been rendered. For example, you might want to change the slider range to some preset value, or change the track highlight to indicate a valid range.

Currently there is nothing observing the property setters. You’ll need to add that functionality to your control. You need to implement property observers that update the control’s frame or drawing. Open up RangeSlider.swift and change the property declarations of the following properties like this:

var minimumValue: Double = 0.0 {
    didSet {
        updateLayerFrames()
    }
}
 
var maximumValue: Double = 1.0 {
    didSet {
        updateLayerFrames()
    }
}
 
var lowerValue: Double = 0.2 {
    didSet {
        updateLayerFrames()
    }
}
 
var upperValue: Double = 0.8 {
    didSet {
        updateLayerFrames()
    }
}
 
var trackTintColor: UIColor = UIColor(white: 0.9, alpha: 1.0) {
    didSet {
        trackLayer.setNeedsDisplay()
    }
}
 
var trackHighlightTintColor: UIColor = UIColor(red: 0.0, green: 0.45, blue: 0.94, alpha: 1.0) {
    didSet {
        trackLayer.setNeedsDisplay()
    }
}
 
var thumbTintColor: UIColor = UIColor.whiteColor() {
    didSet {
        lowerThumbLayer.setNeedsDisplay()
        upperThumbLayer.setNeedsDisplay()
    }
}
 
var curvaceousness: CGFloat = 1.0 {
    didSet {
        trackLayer.setNeedsDisplay()
        lowerThumbLayer.setNeedsDisplay()
        upperThumbLayer.setNeedsDisplay()
    }
}

Basically, you need to call setNeedsDisplay for the affected layers depending on which property was changed. setLayerFrames is invoked for properties that affect the control’s layout.

Now, find updateLayerFrames and add the following to the top of the method:

CATransaction.begin()
CATransaction.setDisableActions(true)

Add the following to the very bottom of the method:

CATransaction.commit()

This code will wrap the entire frame update into one transaction to make the re-flow rendering smooth. It also disables implicit animations on the layer, just like we did before, so the layer frames are update immediately.

Since you are now updating the frames automatically, every time the upper and lower values change, find the following code in continueTrackingWithTouch and delete it:

// 3. Update the UI
CATransaction.begin()
CATransaction.setDisableActions(true)
 
updateLayerFrames()
 
CATransaction.commit()

That’s all you need to do in order to ensure the range slider reacts to property changes.

However, you now need a bit more code to test your new macros and make sure everything is hooked up and working as expected.

Open up ViewController.swift and add the following code to the end of viewDidLoad:

let time = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
    self.rangeSlider.trackHighlightTintColor = UIColor.redColor()
    self.rangeSlider.curvaceousness = 0.0
}

This will update some of the control’s properties after a 1 second pause. You change the track highlight color to red, and change the shape of the range slider and its thumbs.

Build and run your project. After a second, you should see the range slider change from this:

to this:

How easy was that?

The code you just added to the view controller illustrates one of the most interesting, and often overlooked, points about developing custom controls – testing. When you are developing a custom control, it’s your responsibility to exercise all of its properties and visually verify the results. A good way to approach this is to create a visual test harness with various buttons and sliders, each of which connected to a different property of the control. That way you can modify the properties of your custom control in real time — and see the results in real time.

Where To Go From Here?

Your range slider is now fully functional and ready to use within your own applications! You can download the complete project right here.

However, one of the key benefits of creating generic custom controls is that you can share them across projects — and share them with other developers.

Is your control ready for prime time?

Not just yet. Here are a few other points to consider before sharing your custom controls:

Documentation – Every developer’s favorite job! :] While you might like to think your code is beautifully crafted and self-documenting, other developers will no doubt disagree. A good practice is to provide public API documentation, at a minimum, for all publicly shared code. This means documenting all public classes and properties.

For example, your RangeSlider needs documentation to explain what it is — a slider which is defined by four properties: minimumValue, maximumValue, lowerValue, and upperValue — and what it does — allows a user to visually define a range of numbers.

Robustness – What happens if you set the upperValue to a value greater than the maximumValue? Surely you would never do that yourself – that would be silly, wouldn’t it? But you can guarantee that someone eventually will! You need to ensure that the control state always remains valid — despite what some silly coder tries to do to it.

API Design – The previous point about robustness touches on a much broader topic — API design. Creating a flexible, intuitive and robust API will ensure that your control can be widely used, as well as wildly popular.

API design is a topic of great depth, and one which is out of scope for this tutorial. If you are interested, Matt Gemmell’s 25 rules of API design comes highly recommended.

There are a number of places to start sharing your controls with the world. Here are few suggestions of places to start:

  • GitHub – GitHub has become one of the most popular places to share open source projects. There are already numerous custom controls for iOS on GitHub. What’s great about GitHub is that it allows people to easily access your code and potentially collaborate by forking your code for other controls, or to raise issues on your existing controls.
  • CocoaPods – To allow people to easily add your control to their projects, you can share it via CocoaPods, which is a dependency manager for iOS and OSX projects.
  • Cocoa Controls – This site provides a directory of both commercial and open source controls. Many of the open source controls covered by Cocoa Controls are hosted on GitHub, and it’s a great way of promoting your creation.

Hopefully you’ve had fun creating this range slider control, and perhaps you have been inspired to create a custom control of your own. If you do, please share it in the comments thread for this article — we’d love to see your creations!

How To Make a Custom Control in Swift is a post from: Ray Wenderlich

The post How To Make a Custom Control in Swift appeared first on Ray Wenderlich.

Arduino Tutorial: Integrating Bluetooth LE and iOS

$
0
0

Creating machines that interact with the physical world is an incredibly satisfying thing. Controlling them via Bluetooth with your iOS device is just plain awesome!

Create an Arduino Bluetooth accessory!

Create an Arduino Bluetooth accessory!

In the past, the only way to create an Apple-approved Bluetooth device was by being part of the MFi program. With Bluetooth Low Energy 4.0, individuals and small companies can develop their own products and market them without the overhead of Apple’s MFi regulations. That means you can talk to devices over Bluetooth LE with very little configuration and code, which opens up an entire world of opportunity for iOS developers.

This tutorial will teach you how to create your own Bluetooth LE device using standard off-the-shelf components that you can control wirelessly with a simple iOS app. Since the focus of this project is building a BLE device, you’ll be using the iOS Core Bluetooth framework. If you’re unfamiliar with Core Bluetooth, check out our Introduction to Core Bluetooth: Building a Heart Rate Monitor tutorial.

Let the building begin!

Getting Started

Using off-the-shelf components for this build makes creating a Bluetooth device a snap. Here’s an image showing the basic elements you’ll use in this project:

All Parts Needed

A look at the parts you’ll need to complete this project.

Here’s the list of parts you’ll need to complete the project, and where to obtain them:

BLE Shield

Note: You can use a standard Arduino Uno R3 with a USB A to B cable if you already have one. Both will work with this BLE device.

The Arduino, servo and jumper wires can be purchased together from SparkFun.com in the SparkFun Inventor’s Kit for Arduino. This kit comes with many useful components and tutorials to get you started in the hardware world.

You can find less-expensive BLE Shields other than the one listed in this tutorial, but be aware that many of them sacrifice flexibility to save on cost. The BLE-Shield v2.0.0 allows you to program your own custom BLE Services and Characteristics onto the module. This means you can take full advantage of the Bluetooth 4.0 data structure.

That takes care of the hardware side of things – there’s a few extra software pieces to take care of as well.

Download the Xcode starter project here. The starter project includes the view controller and base Core Bluetooth implementation to save you time. You’ll add more code to interface with the BLE Shield later on in the tutorial.

Next, download and install the Arduino IDE from the Arduino Download page; it’s available for Windows, Mac OS X and Linux platforms. You’ll use the Arduino IDE to write and compile the code for the Arduino. Ensure you grab the latest release version, not the beta or nightly builds.

Note: Depending on the operating system and version of your Arduino board, you might need to install additional drivers in order to communicate with the Arduino. To see if you need to perform this extra step, check the Getting Started with Arduino page, select which operating system you’re using and follow the step-by-step instructions provided.

The Basic Design of your App

Your finished project will consist of an iOS app that will send messages via Bluetooth to the BLE Shield module. The module will then send the message to the Arduino board to tell the servo which position it should rotate to.

Here’s an example use-case of your project:

  1. The user of the iOS app moves the slider to the middle position.
  2. The app sends the number 90, which represents 90 degrees in this case, to the BLE Shield module using the active Bluetooth connection.
  3. The BLE Shield transfers the number 90 to the Arduino board.
  4. The Arduino board rotates the servo to the 90 degree position.

Seems pretty straightforward! To begin, you’ll work with the Arduino IDE and program the board logic.

Note: The BLE Shield should not be assembled onto the Arduino board at this time! You may encounter difficulty programming the Arduino if the BLE Shield is installed. As long as you remove it before programming the Arduino, everything should go smoothly. When you assemble or disassemble the BLE shield module or any of the wires, it’s a good idea to remove power from the board by unplugging the USB cable first.

Programming the Arduino

Start the Arduino IDE; you’ll see the editor appear with a blank document, or “sketch”, as shown below:

Arduino IDE 001

Before doing anything else, click File\Save in the top menu and save the current file to a convenient location as Arduino_Servo_Controller. This creates a folder in your save location that contains a file with a .ino file extension.

Note: Don’t rename Arduino folders unless you also rename the .ino file contained within. The Arduino IDE requires that the .ino file reside in a folder with the same name.

Before you start writing code, you’ll need to set up the IDE to communicate with the Arduino Uno board.

Select Tools\Board\Arduino Uno to let the IDE know what kind of board you’ll be dealing with. Next, connect the Uno to your computer with the USB cable as shown below:

Arduino Kit

This lets the Arduino IDE recognize the serial port to which the Uno is connected.

Note: The USB cable provides not only communication to the Arduino, but power as well. If you’re working on a future project that requires more than 500mA, you’ll have to use some other power source besides USB.

Once that’s done, select Tools\Serial Port\… and select the USB port the Arduino is connected to. Generally it’s similar to /dev/tty.usbserial… or /dev/tty.usbmodem….

At this point the Arduino IDE is ready for programming.

Add the code below to your project using the Arduino IDE:

// Arduino Bluetooth LE Servo Controlled by iOS
 
void setup() // Called only once per startup
{ 
} 
 
void loop() // Continuous loop
{
}

Arduino programs are typically split into two main functions: setup() and loop().

The setup() function does exactly what it is says: it’s called only once on startup and is a great place for setting up your hardware and software. The loop() function is called once setup() is done. Once you enter loop() you never exit, but continually execute the code within in linear order until the board is reset or powered down.

Click the Verify button (the checkmark icon) to ensure everything compiles correctly. If so, you’ll see a confirmation message similar to below:

BLE-Success-Build

Now that you have the basic framework of the Arduino program in place, it’s time to add some logic to control the board.

Illuminating an LED

The main objective of your Arduino program in this project is to receive the one-byte messages coming from the BLE Shield and use the contents of the message to set the servo’s position. But before you get too involved into the servo code, it would be nice to test your current connections.

The easiest way to do this is to write a small program that controls the built-in LED on the Uno. Think of it as the “hello world” equivalent on the Arduino. :]

Replace the code in your project with the following:

// Arduino Bluetooth LE Servo Controlled by iOS
 
int LED = 13;     // Most Arduino boards have an onboard LED on pin 13
 
void setup()  // Called only once per startup
{ 
  pinMode(LED, OUTPUT);     // Set pin as an output
  digitalWrite(LED, HIGH);  // Turn on LED (ie set to HIGH voltage)
} 
 
void loop() // Continuous loop
{
  delay(500);
  digitalWrite(LED, LOW);
  delay(500);
  digitalWrite(LED, HIGH);
}

Since most Arduino boards have an onboard LED on pin 13, you can use the illumination of the LED as confirmation that your Arduino program is executing properly. You set the LED variable to 13 to refer to that pin.

In setup(), you set the initial state of the board. pinMode(LED, OUTPUT) sets pin 13 as a digital output, which means that the voltage on this pin can be set to HIGH (+5v) or LOW (0v). digitalWrite() sets the pin’s voltage; in the code above you’ve set it to HIGH to illuminate the LED.

After setup() has finished, loop() will run continuously. delay() will wait a certain number of milliseconds which you’ll use to get the LED to blink. After a 500ms delay, you turn the LED off by writing the value LOW; after another delay, you turn the LED back on.

Click the Verify button again to compile the program and check for any syntax errors. If your program compiles without issue, you’ll see Done Compiling in the status bar of the Arduino IDE. If not, read the status bar message for an indication of where you’ve messed up, fix the error and click the Verify button to make sure you’ve squashed the bug for good.

Now it’s time to program the code to the Arduino board and test the LED.

Make sure your Arduino is connected to your computer with the USB cable then click the Upload button (the one with the right arrow icon) to program the Arduino board. Once the upload process has finished, the Done uploading message should appear in the status bar as shown below:

Click Upload. Done uploading. Success!

Check your Arduino board, and you should see the LED start to blink.

LED 13 is ON. Houston we have communication.

LED 13 is ON. Houston, we have communication.

If an error occurred, try the following things to diagnose your issue:

  • Check that Arduino Uno is selected in the Tools/Board menu.
  • Check that /dev/tty.usbserial… or /dev/tty.usbmodem… (or similar) is selected in the Tools/Serial Port menu.
  • Check that you installed any required drivers for your Arduino board as noted earlier.
  • Check that the BLE Shield is not assembled on the Arduino board during programming of the board.

If the LED works, you can be sure that the Arduino IDE is set up properly. You can now add some code to control the servo.

Interfacing With the Servo Control

A servo is a motor with a changeable rotational position. You control a servo by sending different frequencies of electric pulses to its control line.

Replace the blinking LED code in the current sketch with the following:

// Arduino Bluetooth LE Servo Controlled by iOS
 
#include <Servo.h>
 
int LED = 13;     // Most Arduino boards have an onboard LED on pin 13
Servo myservo;    // Create servo object to control the servo

The above code imports the Servo library and create a variable as an instance of the Servo class. By including the Servo library, you can create Servo objects which can be easily configured to control the servo hardware.

Next, add the following function:

void setup()  // Called only once per startup
{ 
  pinMode(LED, OUTPUT);     // Set pin as an output
  digitalWrite(LED, HIGH);  // Turn on LED (ie set to HIGH voltage)
 
  myservo.attach(9);        // Attach the servo object to pin 9
  myservo.write(0);         // Initialize servo position to 0
}

myServo.attach(9) indicates that output pin 9 of the Arduino Uno board will be connected to the servo, while write(0) sets the position of the servo to zero degrees.

That takes care of the servo initialization, but you’ll still need some code to handle the BLE communication. Before you tackle that, you should read through the following section on the fundamentals of serial communication.

Serial Communication

Serial communication is much like using a telephone. The speaker held to your ear is the receiver (RX) and the microphone is the transmitter (TX). Your transmitter is connected to the receiver of the person with whom you are talking, and your receiver is connected to their transmitter. This allows for bi-directional communication where either person can send or receive information.

Note: The Arduino’s Serial Port communicates using standard TTL voltage levels (0-5v). This type of communication requires three connections: transmit (TX), receive (RX), plus a common ground. The BLE Shield was designed to communicate with the Arduino’s serial port; therefore, its TX line will connect to the Arduino’s RX port and, conversely, its RX line will connect to the Arduino’s TX port.

By default, the Arduino uses pin 0 and pin 1 for serial communication. To communicate with the BLE Shield, this needs to be changed to pin 4 (RX) and pin 5 (TX). This guards against any data collision when the PC is uploading to the Arduino.

Add the following include to the top of the file:

#include <SoftwareSerial.h>

This will allow you to use the serial communication library.

Add the following variable underneath the LED and myservo variable declarations:

SoftwareSerial BLE_Shield(4,5);

This will create an instance of SoftwareSerial named BLE_Shield. The two parameters are pin numbers, so the BLE shield will use RX and TX lines 4 and 5 respectively.

Add the following line to the end of the setup() function:

BLE_Shield.begin(9600);   // Setup the serial port at 9600 bps. This is the BLE Shield default baud rate.

This sets the serial communication, or baud rate, which is the speed at which data bits are transmitted. In order for two devices to communicate, they must use the same baud rate. Since the BLE Shield’s default baud rate is 9600, you set the Arduino serial port to match.

Finally, add the following function to the bottom of the sketch:

void loop()  // Continuous loop
{
  // See if new position data is available
  if (BLE_Shield.available()) {
    myservo.write(BLE_Shield.read());  // Write position to servo
  }
}

Eventually, your iOS device will be feeding data to the BLE shield. loop() has one job now: look for new bytes from the BLE shield’s RX line and pass them to the servo.

First, it calls BLE_Shield.available() to see if any bytes are available. If so, read the available bytes and write them to myServo. If there is no new data from the BLE Shield, then it loops around and checks again…and again…and again.

Save your Arduino project then click the Upload button to upload your new program to the Arduino board. The Done uploading message should appear at the bottom of the IDE. You’ll see the LED light up and stay lit, letting you know main() finished.

At this point, your Arduino code is complete and ready for communication with your iOS app via the BLE Shield.

The next logical step is to get familiar with how the BLE Shield functions; you’ll cover this in the next section.

The Inner Workings of the BLE Shield

The BLE Shield V2.0.0 from Seeed Studio is based on Bluegiga’s BLE112 module which supports Bluetooth Low Energy 4.0. The BLE112, with or without the Shield component, is a great module for interfacing with iOS devices. It can be programmed with custom scripts to run without the need for an additional processor.

In this project, you’ll focus on using the BLE Shield as comes out of the box. It’s preprogrammed with a BLE Service and some characteristics geared toward basic communication.

What type of communication do you think will be used between the Arduino board and BLE Shield? Hint: The answer was mentioned earlier in the Serial Communication section.

Solution Inside: Solution Inside SelectShow>

Here are the preprogrammed Service and Characteristics that come with this specific BLE device as found in the BLE-Shield firmware repository on GitHub:

  • Service
    Name: BLE Shield Service v2.0.0
    UUID: B8E06067-62AD-41BA-9231-206AE80AB550
  • Characteristic
    Name: Bluetooth Device Address
    UUID: 65C228DA-BAD1-4F41-B55F-3D177F4E2196
  • Characteristic
    Name: RX
    UUID: F897177B-AEE8-4767-8ECC-CC694FD5FCEE
    Description: The RX characteristic is used to receive data on the iOS Side. Data written to the BLE-Shield’s TX pin will be notified on the RX characteristic on the iOS device.
  • Characteristic
    Name: TX
    UUID: BF45E40A-DE2A-4BC8-BBA0-E5D6065F1B4B
    Description: The iPhone sends data to the BLE-Shield’s TX characteristic which is then received on the RX pin of the BLE-Shield. The maximum amount of data which can be sent at once is 20 bytes due to BLE112 restrictions.
  • Characteristic
    Name: Baudrate
    UUID: 2FBC0F31-726A-4014-B9FE-C8BE0652E982
    Supported baud rate values:
    0×00 = 9600 Baud (default)
    0×01 = 14400 Baud
    
0×02 = 19200 Baud

    0×03 = 28800 Baud
    0×04 = 38400 Baud

Since your project is based on one-way communication from your iOS device to the Arduino board, you’ll only make use of the TX characteristic.

Hardware Connections

Now it’s time to work on the hardware connections of the project. Be sure the USB cable is unplugged from the Arduino board before working with the hardware.

Note: The Arduino saves the last-uploaded sketch to its internal memory. The next time you connect the USB cable, the sketch you just wrote will run automatically without clicking the Upload button from the Arduino IDE!

To make your life easier, gather the following parts below before you begin:

  • Arduino Uno Board
  • BLE-Shield
  • Servo
  • Three jumper wires

The first place to start is configuring the RX/TX jumpers. The BLE Shield default jumper position connects its RX to pin 0 and TX to pin 1. To match the Arduino code above, you’ll need to set RX to pin 4 and TX to pin 5.

Move the jumpers on the board to the appropriate pins as indicated in the image below:

Move BLE Shield Jumpers RX and TX to the indicated positions.

Move BLE Shield Jumpers RX and TX to the indicated positions.

Assemble the BLE Shield onto the Arduino board as shown below:

Attaching BLE Shield to Arduino board

Attaching BLE Shield to Arduino board

Be sure to match up the pins before applying any pressure to seat the BLE Shield. Inserting misaligned pins can cause damage to the hardware.

Once you’re done, the entire assembly should look like this:

BLE Shield Assembled!

BLE Shield Assembled!

The servo motor requires three connections: +5v (red), Ground (black) and Position Signal (white). The +5v and Ground connections provide the servo with the power it needs to change and maintain its rotational position. The Position Signal connection is used to set the position of the servo. To prevent possible damage to the servo, ensure you don’t swap any of the connections.

Note: A servo’s position is based on the frequency of electric pulses sent to the servo’s Position Signal wire. If you were to connect this wire to an oscilloscope you would see a square wave pattern with a specific frequency and duty cycle. As the frequency changes, the servo motor turns its shaft according to the frequency of the received pulse.

Attach the three wires to the servo motor as shown in the image below:

Assembled Servo Wires

It’s a good idea to use the same color wires as the servo leads so that you don’t get mixed up as to which wire goes where.

Connect the red wire to the BLE Shield’s 5v pin. Connect the black wire to the GND pin and the white wire to pin 9 as shown below:

Wires Connected to BLE Shield

Wires Connected to BLE Shield

Your hardware is connected and ready to go. All that’s left is to finish writing your iOS app to tie it all together!

Fleshing out the iOS App

The app uses a slider to wirelessly control the position of the servo. When you move the slider button up and down, the position data will be sent to the Arduino’s BLE Shield. The Connect/Disconnect icon at the bottom will indicate the Bluetooth connection status.

Open the downloaded starter project in Xcode, plug in your iPhone and build and run your app. The app will open and you will see an image similar to the one below:

Arduino Servo App

Arduino Servo App

The app isn’t quite ready to communication with the Arduino, but that’s where you come in.

Click the Stop button in Xcode.

Starter Project Overview

When the app starts, RWTViewController creates an instance of BTDiscovery to begin searching for BLE devices in range with a specific Service UUID. BTDiscovery manages the discovery of and connections to Bluetooth devices. It also notifies the app when a Bluetooth device connects or disconnects.

RWTViewController has a vertical slider control that sends the slider’s position value to BTService and updates the Bluetooth image with the connection status. BTService handles the communication to the BLE shield module.

Before moving on, open BTService.h — there are a few important things in this file that help the app communicate with the BLE Shield, so it’s worth taking a minute to review them.

Towards the top of the file you’ll see a couple of UUID #defines.

RWT_BLE_SERVICE_UUID represents the 128-bit service UUID of the Arduino’s BLE Shield. You’ll notice that this UUID matches the BLE Shield Service v2.0.0 UUID listed earlier in the tutorial. Similarly, RWT_POSITION_CHAR_UUID matches the BLE Shield’s TX UUID. Both hex values must match in order for the app to discover and communicate with the BLE Shield.

Finally, RWT_BLE_SERVICE_CHANGED_STATUS_NOTIFICATION notifies the rest of the app when a peripheral connects or disconnectes from the app. The main view controller of your app uses this value to toggle the connection image on the home screen.

No code needs to be added here, so you can move on.

Open RWTViewController.m and replace the implementation of sendPosition: with the following:

- (void)sendPosition:(uint8_t)position {
  // Valid position range: 0 to 180
  static uint8_t lastPosition = 255;
 
  if (!self.allowTX) { // 1
    return;
  }
 
  // Validate value
  if (position == lastPosition) { // 2
    return;
  }
  else if ((position < 0) || (position > 180)) { // 3
    return;
  }
 
  // Send position to BLE Shield (if service exists and is connected)
  if ([BTDiscovery sharedInstance].bleService) { // 4
    [[BTDiscovery sharedInstance].bleService writePosition:position];
    lastPosition = position;
 
    // Start delay timer
    self.allowTX = NO;
    if (!self.timerTXDelay) { // 5
      self.timerTXDelay = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerTXDelayElapsed) userInfo:nil repeats:NO];
    }
  }
}

This method is called from positionSliderChanged: each time the user moves the slider. Here’s what happens in this method:

  1. Only continue if no other send occurred in the last 0.1 seconds to avoid flooding the BLE connection with too much data.
  2. Prevent the same value from being written multiple times. To keep traffic and power use to a minimum, you always want to take advantage of any opportunity to prevent sending duplicate data.
  3. Make sure that the values being sent are in the correct range. The Arduino’s Servo object accepts values between 0 and 180. If you take a peek at the storyboard file, you will see that the slider’s minimum and maximum values have been set to the same range.
  4. Ensure that the BTService exists and is ready for action, then write the slider value to the position characteristic.
  5. The timer prevents you from flooding the BLE Shield with data. First, you set self.allowTX to NO while sending data to the shield and then reset it back to YES after the delay.
Note: What’s the significance of using a timer here? Well, BLE devices are low energy devices — which translates into “low horse power”. Also recall that the baud rate between the Arduino and the BLE Shield is set at 9600 bps. If you are building an application with large volumes of data, increase the baud rate if you can to preserve energy.

Now you have to implement the part of BTService to transmit the data. Open BTService.m and replace the implementation of writePosition: with the following:

- (void)writePosition:(UInt8)position {
  // See if characteristic has been discovered before writing to it
  if (!self.positionCharacteristic) {
    return;
  }
 
  NSData *data = nil;
  data = [NSData dataWithBytes:&position length:sizeof(position)];
  [self.peripheral writeValue:data
            forCharacteristic:self.positionCharacteristic
                         type:CBCharacteristicWriteWithResponse];
}

To start, the method will return immediately if the proper characteristic matching the UUID in BTService.h hasn’t been discovered yet. If it has, you need to wrap the data — simply the position value — in an NSData object. Then, you write the value out to the Core Bluetooth peripheral.

Since your positionCharacteristic UUID matches the BLE Shield’s TX characteristic, the BLE Shield transmits the data to the RX of the Arduino. The Arduino code written earlier uses this piece of data to set the servo’s position.

Build your app to make sure everything compiles. You app is now complete and ready for testing!

Putting it All Together

The Arduino, BLE Shield, and servo should already be fully assembled as shown in the image below:

Arduino Servo Project Completed!

Arduino Servo Project Completed!

Plug the USB cord into the Arduino to supply power. Next make sure your iPhone’s Bluetooth is on and your iPhone is connected to your computer.

Run your app from Xcode; within a couple seconds you should see the app’s Bluetooth icon change from Disconnected to Connected and the BLE Shield’s LED should illuminate. Now move the slider up and down. You should see the servo motor rotate based on the slider movement.

(Here’s a direct link to the QuickTime movie file if you can’t see the above action video.)

Where To Go From Here?

You can download the final project here; it includes both the iOS and the Arduino code.

Hopefully this project has spurred you to dream up other projects that use Bluetooth LE devices and iOS. For example, you could use this tutorial’s servo motor to automatically unlock the front door of your house. Or, you could create an iOS remote control for your water sprinklers and never forget to water your lawn again!

This project gave you a small taste of what it takes to create your own BLE device. If you’re interested in learning more about this topic, you might enjoy my book Integrating iOS Bluetooth LE with PIC18 Microcontrollers. It gives an in-depth look at creating a BLE device at the microcontroller level.

As well, the book provides practical lessons on how to program and configure your own Services and Characteristics for machine control. The main project of the book is based on the iO Xtreme PCB. The book and the iO Xtreme board are available on my website at www.back-40.com.

I hope you enjoyed the tutorial. If you have any questions or comments, please join the discussion in the forums!

Arduino Tutorial: Integrating Bluetooth LE and iOS is a post from: Ray Wenderlich

The post Arduino Tutorial: Integrating Bluetooth LE and iOS appeared first on Ray Wenderlich.

Readers’ App Reviews – August 2014

$
0
0
Censie

Who am I!

August is almost over, and that means its time to showcase some more apps from readers like you!

Each month readers submit a ton of apps for me to review and showcase. This month was no exception, with over 50 apps submitted. As always, I sat down and gave them all a try and picked a few to share with you.

This month we’ve got:

  • An app for the perfect color
  • A way to remember all those borrowed books
  • An alternative to selfies
  • And a game where your cat saves the galaxy!

Better hurry, these apps aren’t going to download themselves!

Color Mate

Colormate
Color Mate is a must have tool for all of us.

Color Mate is packed with features. You can pull colors from pictures, generate random colors on the fly, convert colors from Hex to RGB to HSB to CMYK, analyze colors for temperature and pantones, find shades and tints, and of course save your favorites.

Color Mate also supports searching for colors by name looking through multiple open source dictionaries like Moroney, Wikipedia, and even XKCD.

There isn’t a more full featured color tool out there. You need this today!

Freely – Time Tracking & PDF Invoices

Freely
Freely helps you track your time quickly and easily. And at the end you can send out an invoice of your time straight from the app.

Freely lets you track your time for both Projects as a whole and individual tasks within the project. You can track time off and on for individual tasks so its easy to spread work over several days. It also offers beautiful graphs for a project or week to help you visualize your work.

And you can attach your hourly rate to each project and use it to calculate estimates or final amounts. Then simply add a logo and some client info and you’ll have an invoice ready to go.

Battle Pet Galaxy

battlepet
Have you ever wondered what it would be like if your pet were defending the universe? Wonder no longer!

Battle Pet Galaxy lets your pet save the galaxy from the evil space bunny and his minions. The main character is quite literally your pet. You take a photo of your pet, adorn them with space helmets or cowboy hats, then send them into space where they battle other animals out to destroy the galaxy.

With over 20 different helmets to put on your pet, they’ll be ready for anything. And if they’re just too cute to let go, you can always share a picture of them.

Censie

Censie
In a world of privacy gone awry, Censie offers a very useful and easy way to make our photos a little more private.

Censie does live image processing to pixelate areas of the photo you choose. You can just tap where you’d like it censored, or let facial recognition handle it for you.

Censie also wipes sensitive data like location and camera model from your photos. And since Censie does its censoring on live untaken pictures, no uncensored version of the photo is ever at risk of accidentally popping up.

Trickshot – Football Edition

Trickshot
Trickshot is like playing mini golf mixed with pinball. And its some addicting fun!

Just pull back and the ball goes flying, but aim carefully. You’ve got to get it into the hole with as few kicks as possible.

Trickshot has over 50 levels, GameCenter integration for leaderboards and achievements, and local multiplayer up to 4 players.

The Gamer’s Reader

GamerReader
The Gamer’s Reader is a great news aggregator for all things games. Its got a crisp clean design and news collected from over 30 sites. You can organize it by categories or by sites.

The Gamer’s Reader has 5 different themes and customizations for fonts, text sizes, margins, line spacing, and more. You can view raw websites or let it turn your content in a cleanly designed single page article.

The Gamer’s Reader also help you find all the videos in a dedicated video section. And if you’re not ready to read now, you can always save to read later or offline.

Super Game About Squares

aboutsquares
This puzzle game is easy to play, but hard to master. Your goal is to get colored squares over their matched circle.

Squares have a color and a direction. Tapping them moves them their current direction. But you’ll have to be wise about your order of moves to make sure you get each square where it needs to go. And at higher levels squares can change direction when passing over certain triangles.

All in all its a very addicting and fun puzzle game. Its relaxing to play on your own time, but the puzzles definitely get challenging.

PopSpell Ninja – Spelling for Kids

Popspell
Kids always learn better with some fun, and PopSpell Ninja delivers.

With over 390 words and illustrations there is a lot to learn. And it comes in English, French, Spanish, and Italian!

Gameplay is fun and simple. Letters appear over the illustration in bubbles and a quick ninja swipe puts it in the blanks for the word. All you need to do is pop them in order to spell the word illustrated.

You can play random mode for any word or select from a number of categories like animals or food to help kids learn more related words.

Answer Me That!

answermethat
Answer Me That is a social experiment disguised as a game.

Users ask simple questions of the community. Questions are limited to two options, black or white, yes or now, right or wrong. You can add context, hints. But all in all, its the community’s decision to decide.

In the end everyone is answering each other’s questions, and you begin to survey the users as a whole. Its insanely entertaining to read responses and reasons. And its also a very powerful tool to have the community at large at your fingertips to ask any question you choose.

LendPal

lendpal
Do you lend stuff out to your friends all the time but forget who or when? LendPal will help.

LendPal lets you keep track of what or how much you’ve loaned to who. Along with when you loaned it and reminders for payback.

But LendPal doesn’t stop with you bugging your friends. It also tracks what you’ve borrowed from friends. And of course reminds you to pay them back or return the item.

In a digital world, LendPal is a great way to keep track of your physical stuff as you share it with your friends.



Honorable Mentions

Every month I get more submissions than I can handle. I give every app a shot, but I can’t write about them all. These are still great apps made by readers like you. Its no a popularity contest or even a favorite picking contest. I just try to get a glimpse of what the community is working on through your submissions. Take a moment and checkout these other great apps I didn’t have time to showcase properly.

Robot Munch
Sara’s Adventure
AnimeHub
Promishare
Spekz
Weenie Rush
Love Your Country
DIY Subtitle Free – Add subtitle, caption, emoji, watermark to your video
Amazing Bobby
Shards – the Brick Breaker
5Squared
Speed Dial
QuickRead – Speed Reader for iOS
Hungry Bird Free
Guess The Number
Hunger Race
Quit: Stop smoking now!
Link Together
Blend Music Player
Tuku Fly Home
Retro Formula
TicTac5
RGBY Quiz
Astra
Unscramble This
Birthday Story
Fan Playbook
Space Swiper
Scoreboards
Showboard
Don’t Miss A Block
Red Dot Blue Dot
Breakdown Buddy
Easpense Tracker
QQISSO?(What’s this)
3pCompass
Halfglass Weather



Where To Go From Here?

Each month, I really enjoy seeing what our community of readers comes up with. The apps you build are the reason we keep writing tutorials. Make sure you tell me about your next one, submit here!

If you’ve never made an app, this is the month! Check out our free tutorials to become an iOS star. What are you waiting for – I want to see your app next month!

Readers’ App Reviews – August 2014 is a post from: Ray Wenderlich

The post Readers’ App Reviews – August 2014 appeared first on Ray Wenderlich.

Video Tutorial: Introduction to Unity Part 5: Layers and Tags


UIKit Dynamics Tutorial: Tossing Views

$
0
0
Learn how to toss your views with gestures and physics behaviors in this UIKit Dynamics tutorial!

Learn how to toss your views with gestures and physics behaviors in this UIKit Dynamics tutorial!

In this UIKit Dynamics tutorial, you’ll learn how to toss views off-screen with gestures in a natural feeling way.

You may have seen this technique popularized in the popular app Tweetbot.

This tutorial is a good fit for intermediate developers because it covers some neat effects, such as rotation and fly-away animations using the native UIKit framework.

But if you’re completely new to UIKit dynamics, don’t fear – this tutorial will guide you through step by step.

Without further ado, let’s get to tossing!

Note from Ray: Full credit for this technique goes to Orta Therox, the newest member of our new “Code Team” at raywenderlich.com. Orta created the original sample project that Tony polished and converted into this tutorial.

The Code Team is a group of expert-level coders that spend their waking hours coming up particularly cool demos to demonstrate advanced techniques. The Tutorial Team then converts the best demos into high quality tutorials. If you’re an expert-level iOS developer and joining the Code Team piques your interest, contact me!

Getting Started

Note: This section is optional for those who want to make the project from scratch. Experienced iOS developers might want to skip this section and proceed to the next, where we have a starter project waiting for you.

Start up Xcode, select File\New\Project…, choose iOS\Application\Single View Application template, and click Next. Name the project DynamicToss and set the device family to iPhone.

Now select the project name on the left and make sure to select General at the top of the Xcode window. Under Deployment Info/Device Orientation uncheck the ‘Landscape Left’ and ‘Landscape Right’ checkboxes, because your app will only run in portrait mode.

002_PortraitOnly

Next, download an image that you’ll need for this project, courtesy of gameartguppy.com.

goldfish_feature

Unzip the file and add it to your project.

Next, open Main.storyboard and add an image view with the following values: (X=33, Y=137, Width=254, Height=172, Image=goldfish_feature.jpg). Your screen should now look like this:

003_Goldfish

Next, drag two UIViews into your view controller that you will use to help track your gestures. Set them to the following settings:

  • View 1: (X=156, Y=219, Width=8, Height=8, Background=red)
  • View 2: (X=80, Y=420, Width=8, Height=8. Background=blue)

When done your view should look like this:

Screen Shot 2014-05-20 at 8.05.13 PM

Add the following private properties to RWTViewController.m:

@property (nonatomic, weak) IBOutlet UIView *image;
@property (nonatomic, weak) IBOutlet UIView *redSquare;
@property (nonatomic, weak) IBOutlet UIView *blueSquare;
 
@property (nonatomic, assign) CGRect originalBounds;
@property (nonatomic, assign) CGPoint originalCenter;
 
@property (nonatomic) UIDynamicAnimator *animator;
@property (nonatomic) UIAttachmentBehavior *attachmentBehavior;
@property (nonatomic) UIPushBehavior *pushBehavior;
@property (nonatomic) UIDynamicItemBehavior *itemBehavior;

Select the Main.storyboard file and right click on the View Controller. Drag from the circle to the right of the outlet named blueSquare onto the small blue square view and release the mouse. This links the property to the view object.

Do the same for the red square, and finally for the property named image, drag onto your jetpack-equipped goldfish. Your three view properties should now be linked, like this:

finallinking

The red and blue squares will represent points that the UIDynamics physics engine uses to animate the image.

The blue square will simply represent where your touch began, i.e., where your finger first made contact with the screen. The red square will track your finger as it moves.

You’ll configure the UIDynamics framework so that when you move that point around, the image view physically animates them to move in tandem.

There’s one final thing you need to do – set up a gesture recognizer for the view. Open RWTViewController.m and add this new method to the file:

- (IBAction) handleAttachmentGesture:(UIPanGestureRecognizer*)gesture
{
    CGPoint location = [gesture locationInView:self.view];
    CGPoint boxLocation = [gesture locationInView:self.image];
 
    switch (gesture.state) {
    case UIGestureRecognizerStateBegan:{
        NSLog(@"you touch started position %@",NSStringFromCGPoint(location));
        NSLog(@"location in image started is %@",NSStringFromCGPoint(boxLocation));
 
        break;
    }
    case UIGestureRecognizerStateEnded: {
        NSLog(@"you touch ended position %@",NSStringFromCGPoint(location));
        NSLog(@"location in image ended is %@",NSStringFromCGPoint(boxLocation));
 
        break;
    }
    default:
        break;
    }
}

You’re going add a gesture recognizer to detect dragging, aka panning, and call this method that occurs. For now, this method simply displays the position of your finger in two coordinate systems (the view, and the image view).

To add the gesture recognizer, open Main.storyboard and drag a Pan Gesture Recognizer into the View. Then control-drag from the Pan Gesture Recognizer up to your View Controller, and connect it to the handleAttachmentGesture: action.

Now build and run. Swipe or drag across the screen and you should see messages coming out on the debug window:

2014-07-13 14:01:49.666 DynamicToss[3999:60b] you touch started position {127, 365}
2014-07-13 14:01:49.667 DynamicToss[3999:60b] location in image started is {94, 228}
2014-07-13 14:01:50.097 DynamicToss[3999:60b] you touch ended position {113, 464}
2014-07-13 14:01:50.097 DynamicToss[3999:60b] location in image ended is {80, 327}

Great! You’ve got the basic UI set up – now it’s time to make it dynamic.

UIDynamicAnimator and UIAttachmentBehavior

Note: If you skipped ahead from the previous section, download the starter project to continue from this point.

The first thing you want to do is make the image view move as you drag it. You will do this with a type of UIKit Dynamics class called a UIAttachmentBehavior.

Open RWTViewController.m and place the following code in the viewDidLoad method underneath [super viewDidLoad].

self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.originalBounds = self.image.bounds;
self.originalCenter = self.image.center;

The above code sets up a UIDynamicAnimator, which is UIKit’s engine for physics-based animation. The reference view self.view defines the coordinate system for the animator.

You add behaviors to an animator, which allow you to do things like attaching views, pushing views, making them be affected by gravity, and more.

Let’s add your first behavior to this animator. You’ll start with a UIAttachmentBehavior, to make the image view track your finger when you make a pan gesture.

To do this, add the following code to handleAttachmentGesture:, underneath the two NSLog statements in the case UIGestureRecognizerStateBegan section:

// 1
[self.animator removeAllBehaviors];
 
// 2        
UIOffset centerOffset = UIOffsetMake(boxLocation.x - CGRectGetMidX(self.image.bounds),
                                     boxLocation.y - CGRectGetMidY(self.image.bounds));
self.attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.image
                                                    offsetFromCenter:centerOffset
                                                    attachedToAnchor:location];
// 3
self.redSquare.center = self.attachmentBehavior.anchorPoint;
self.blueSquare.center = location;
 
// 4
[self.animator addBehavior:self.attachmentBehavior];

Let’s go over this section by section:

  1. This first removes any existing animation behaviors that might be hanging around.
  2. Second this creates a UIAttachmentBehavior that attaches the the point inside the image view where the user taps to an anchor point (which happens to be the exact same point). Later on, you will change the anchor point, which will cause the image view to move.


    Attaching an anchor point to a view is like installing an invisible rod that connects the anchor point to a fixed attachment position on the view.

  3. Updates the red square to indicate the anchor point, and the blue square to indicate the point inside the image view that it is attached to (right now these should be the same).
  4. Adds this behavior to the animator to make it take effect.

Next you need to tell the anchor point itself to follow your finger. To do this, add the following code to handleAttachmentGesture: in the default section (which occurs as the gesture changes):

[self.attachmentBehavior setAnchorPoint:[gesture locationInView:self.view]];
self.redSquare.center = self.attachmentBehavior.anchorPoint;

This simply aligns the anchor point and red square to the finger’s current position. When your finger moves, the gesture recognizer calls this method to update the anchor point to follow your touch. In addition, the animator automatically updates the view to follow the anchor point.

Build and run, and you are now able to drag the view around:

Dragged View

However it would be nice to return the view back to its original position when you’re done dragging. To fix this, add this new method to the file:

- (void)resetDemo
{
    [self.animator removeAllBehaviors];
 
    [UIView animateWithDuration:0.45 animations:^{
        self.image.bounds = self.originalBounds;
        self.image.center = self.originalCenter;
        self.image.transform = CGAffineTransformIdentity;
    }];
}

Then call this in handleAttachmentGesture:, underneath UIGestureRecognizerStateEnded section:

[self resetDemo];

Build and run, and now after you drag an image it should revert to its original position.

UIPushBehavior

Next, you need to detach the view when you stop dragging, and endow it with momentum so that it can continue its trajectory when you release it while in motion. You will do this with a UIPushBehavior.

First, you’ll need two constants. Add these to the top of the file:

static const CGFloat ThrowingThreshold = 1000;
static const CGFloat ThrowingVelocityPadding = 35;

ThrowingThreshhold indicates how fast the view must be moving in order to have the view continue moving (versus immediately returning to its original spot). ThrowingVelocityPadding is a magic constant that affects how fast or slow the toss should be (this was chosen by trial and error).

Finally, inside handleAttachmentGesture:, replace the UIGestureRecognizerStateEnded case with the following:

case UIGestureRecognizerStateEnded: {
    [self.animator removeBehavior:self.attachmentBehavior];
 
    //1
    CGPoint velocity = [gesture velocityInView:self.view];
    CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
 
    if (magnitude > ThrowingThreshold) {
        //2
        UIPushBehavior *pushBehavior = [[UIPushBehavior alloc]
                                        initWithItems:@[self.image]
                                        mode:UIPushBehaviorModeInstantaneous];
        pushBehavior.pushDirection = CGVectorMake((velocity.x / 10) , (velocity.y / 10));
        pushBehavior.magnitude = magnitude / ThrowingVelocityPadding;
 
        self.pushBehavior = pushBehavior;
        [self.animator addBehavior:self.pushBehavior];
 
        //3
        NSInteger angle = arc4random_uniform(20) - 10;
 
        self.itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.image]];
        self.itemBehavior.friction = 0.2;
        self.itemBehavior.allowsRotation = YES;
        [self.itemBehavior addAngularVelocity:angle forItem:self.image];
        [self.animator addBehavior:self.itemBehavior];
 
        //4
        [self performSelector:@selector(resetDemo) withObject:nil afterDelay:0.4];
    }
 
    else {
        [self resetDemo];
    }
 
    break;
}

Let’s go over this section by section:

  1. Ask the gesture for the velocity of the drag.


    Using velocity and your old friend the Pythagorean theorem, you compute the magnitude of the velocity — which is the hypotenuse of the triangle formed from the x direction velocity and the y direction velocity.


    To understand the theory behind this check out this Trigonometry for Game Programming tutorial.

  2. Assuming the gesture magnitude exceeds your minimum threshold set up for the action, you set up a push behavior.


    A push behavior applies a continuous — or instantaneous — force to the specified items. In this case, it’s an instantaneous force against the image.


    The desired direction is composed of the x and y velocities converted to a vector that gives the directional portion. Finally, the push behavior is added to the animation sequence.

  3. This section sets up some rotations to make the image “fly away.” You can read up on the complicated math here.


    Some of this depends on how close to the edge your finger is when it initiates the gesture.


    Play around with the values here and watch how the movements change the effects. The values used give a nice, flowing rotation with a cool spinning effect!

  4. After a specified interval of time, the animation resets by sending the image back to its destination, so it zips off and returns to the screen — just like a ball bouncing off a wall!

Build and run, and you should now be able to toss your view offscreen in a pleasing manner!

Learn how to make tossable views with UIKit Dynamics!

Where To Go from Here?

Here is the final example project from this UIKit Dynamics tutorial.

Congratulations, you’ve now learned how to do some fancy UIKit dynamic animations that can give an app’s UI some pretty sweet effects.

If you want learn more about UIKit Dynamics, check out the two UIKit Dynamics chapters in iOS 7 for Tutorials.

Drop by the forums or leave a comment below to share your successes or ask questions about making cool animations in iOS. And use your new powers wisely!

UIKit Dynamics Tutorial: Tossing Views is a post from: Ray Wenderlich

The post UIKit Dynamics Tutorial: Tossing Views appeared first on Ray Wenderlich.

Using NSURLProtocol with Swift

$
0
0
NSURLProtocol Tutorial

Learn how to harness the power of NSURLProtocol!

Update note: This tutorial was updated for iOS 8 and Swift by Zouhair Mahieddine, checked against Xcode 6 beta 7. Original tutorial by Rocir Santiago.

NSURLProtocol is like a magic key to the URL. It lets you redefine how Apple’s URL Loading System operates, by defining custom URL schemes and redefining the behavior of existing URL schemes.

Does that sound magical? It should. Because, if you look for it, I’ve got a sneaky feeling you’ll find URLs — much like love — are all around us. What do UIWebView and WKWebView use? URLs. What’s used for video streaming with MPMoviePlayer? URLs. How do you send someone to your app on iTunes, initiate FaceTime or Skype, launch another app on the system, or even embed an image in an HTML file? With URLs. Have a peek at NSFileManager and notice how many of its file-manipulation methods require and return — URLs.

In this NSURLProtocol tutorial, you’ll learn how to define a protocol handler that modifies URL schemes. It will add a rough and ready transparent caching layer, by storing retrieved resources in Core Data. By enabling it, an ordinary UIWebView can then take on the role of a browser by caching downloaded pages for offline viewing at a later time.

Before you dive in head first, you’ll need a basic understanding of networking concepts and familiarity with how NSURLConnection works. If you are not currently familiar with NSURLConnection then I suggest reading this tutorial and/or this document by Apple.

So are you ready to learn what you can do with NSURLProtocol? Good, go pour yourself a cuppa something and settle in for a meaty, mind-broadening discussion and step-by step exercise.

Getting Started

For this tutorial’s project, you’ll build an elementary mobile web browser, such as one that you might add to your next app. It will have a basic user interface that lets the user enter and go to a URL. The twist is that your browser will cache successfully retrieved results. This way the user can load pages already visited in the blink of an eye, because the page won’t load from a network request, but from the app’s local cache.

You already know that fast page loads == happy users, so this is a good example of how NSURLProtocol can improve your app’s performances.

These are the steps you’re going to go through:

  • Use a UIWebView for displaying the websites
  • Use Core Data for caching the results.

If you’re not familiar with Core Data, you can take a look into our tutorial. However, the code in this tutorial should be enough to understand the possibilities of NSURLProtocol. Using Core Data is just a simple way to implement the local cache, so it’s not essential to learn something useful here.

Starter Project Overview

You can download the starter project here. As soon as the download is finished, unzip it and open the project file.

When you open the project, there are two main files. The first one is the Main.storyboard file. It has the UIViewController set up the way you need for implementation. Notice the UITextField (for URL input), UIButton (for firing the web requests) and UIWebView.

Open BrowserViewController.swift. Here you’ll see the basic behavior set up for the UI components. This UIViewController implements the UITextFieldDelegate protocol, so you can fire the request when the user taps the return key. The IBAction for the button is pre-set to behave the same way as the return key. Last, the sendRequest() method just takes the text from the textfield, creates a NSURLRequest object and calls the loadRequest(_:) method from UIWebView to load it.

Once you’re familiarized with the app, build and run! When the app opens, enter “http://raywenderlich.com” and press the “Go” button. The UIWebView will load the response and display the results in the app. Pretty simple for a starting point. Now it’s time for you to stretch those finger muscles. Up next….coding!

Intercepting network requests

A set of classes known as the URL Loading System handles URL requests on iOS. At the heart of the URL Loading System is the NSURL class. For network requests, this class tells what host your app is trying to reach and path to the resource at that host. In addition the NSURLRequest object adds information like HTTP headers, the body of your message, etc.. The loading system provides a few different classes you can use to process the request, the most common being NSURLConnection and NSURLSession.

Now it’s time to start intercepting all NSURLRequest’s fired by the app. For that, you’ll need to create your own NSURLProtocol implementation.

Click File\New\File…. Select iOS\Source\Cocoa Touch Class and hit the Next button. In the Class field, enter MyURLProtocol and in the Subclass of field, enter NSURLProtocol. Check that the language is set to Swift. Finally, press Next and then Create when the dialog appears.

Open MyURLProtocol.swift and replace its content with the following:

import UIKit
 
var requestCount = 0
 
class MyURLProtocol: NSURLProtocol {
  override class func canInitWithRequest(request: NSURLRequest) -> Bool {
    println("Request #\(requestCount++): URL = \(request.URL.absoluteString)")
    return false
  } 
}

Every time the URL Loading System receives a request to load a URL, it searches for a registered protocol handler to handle the request. Each handler tells the system whether it can handle a given request via its canInitWithRequest(_:) method.

The parameter to this method is the request that the protocol is being asked if it can handle. If the method returns true, then the loading system will rely on this NSURLProtocol subclass to handle the request, and ignore all other handlers.

If none of the custom registered handlers can handle the request, then the URL Loading System will handle it by itself, using the system’s default behavior.

If you want to implement a new protocol, like foo://, then this is where you should check to see if the request’s URL scheme was foo. But in the example above, you’re simply returning false, which tells you your app cannot handle the request. Just hold on a minute, you’ll start handling them soon!

Note: NSURLProtocol is meant to be an abstract class. You create subclasses with the custom behavior for a URL protocol, but you never instantiate NSURLProtocol directly.

Open AppDelegate.swift and replace the application(_:didFinishLaunchingWithOptions:) method with this one:

func application(application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    NSURLProtocol.registerClass(MyURLProtocol)
    return true
}

Now when your app launches, it will register the protocol with the URL Loading System. That means it will have the opportunity to handle every request delivered to the URL Loading system. This includes code which calls the loading system directly, as well as many system components that rely on the URL loading framework, such as UIWebView.

Build and run the project. Insert http://raywenderlich.com as the website, tap on Go and check the Xcode console. Now, for every request the app needs to perform, the URL Loading System asks your class if it can handle it.

In the console you should see something like this:

Request #0: URL = http://raywenderlich.com/
Request #1: URL = http://raywenderlich.com/
Request #2: URL = http://raywenderlich.com/
Request #3: URL = http://raywenderlich.com/
Request #4: URL = http://raywenderlich.com/
Request #5: URL = http://raywenderlich.com/
Request #6: URL = http://www.raywenderlich.com/
Request #7: URL = http://www.raywenderlich.com/
Request #8: URL = http://www.raywenderlich.com/
Request #9: URL = http://www.raywenderlich.com/
Request #10: URL = http://www.raywenderlich.com/
Request #11: URL = http://www.raywenderlich.com/
Request #12: URL = http://raywenderlich.com/
Request #13: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
Request #14: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
Request #15: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1
Request #16: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1
Request #17: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
...

For now, your class is just logging the string representation of the request’s URL and returning false, which means your custom class cannot handle the request. But if you look into the logs, you’ll see all the requests made from the UIWebView. It includes the main website (.html) and all the assets, such as JPEGs and CSS files. Every time the UIWebView needs to fire a request, it’s logged to the console before it’s actually fired. The count should show you a mountain of requests — likely over five hundred — because of all the assets on the web page.

So this is your way in: your custom class is being notified for every URL request, and next you can do something about each request!

Custom URL Loading

“I love it when pages take forever to load” said no user, ever. So now you need to make sure your app can actually handle the requests. As soon as you return true in canInitWithRequest(_:), it’s entirely your class’s responsibility to handle everything about that request. This means you need to get the requested data and provide it back to the URL Loading System.

How do you get the data?

If you’re implementing a new application networking protocol from scratch (e.g. adding a foo:// protocol), then here is where you embrace the harsh joys of application network protocol implementation. But since your goal is just to insert a custom caching layer, you can just get the data by using NSURLConnection.

Effectively you’re just going to intercept the request and then pass it back off to the standard URL Loading System through using NSURLConnection.

Your custom NSURLProtocol subclass returns data through an object that implements the NSURLProtocolClient protocol. There’s a bit of confusing naming to keep straight in your head: NSURLProtocol is a class, and NSURLProtocolClient is a protocol!

Through the client, you communicate to the URL Loading System to pass back state changes, responses and data.

Open MyURLProtocol.swift and add the following property at the top of the MyURLProtocol class definition:

var connection: NSURLConnection!

Next, find canInitWithRequest(_:). Change the return line to return true:

return true

Now add four more methods:

override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
    return request
}
 
override class func requestIsCacheEquivalent(aRequest: NSURLRequest,
                                   toRequest bRequest: NSURLRequest) -> Bool {
    return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)
}
 
override func startLoading() {
    self.connection = NSURLConnection(request: self.request, delegate: self)
}
 
override func stopLoading() {
    if self.connection != nil {
        self.connection.cancel()
    }
    self.connection = nil
}

It’s up to your protocol to define what a “canonical request” means, but at a minimum it should return the same canonical request for the same input request. So if two semantically equal (i.e. not necessarily ===) are input to this method, the output requests should also be semantically equal. For example, if your custom URL scheme is case insensitive then you might decide that canonical URLs are all lower case.

To meet this bare minimum, just return the request itself. Usually, this is a reliable go-to solution, because you usually don’t want to change the request. After all, you trust the developer, right?! An example of something you might do here is to change the request by adding a header and return the new request.

requestIsCacheEquivalent(_:toRequest:) is where you could take the time to define when two distinct requests of a custom URL scheme (i.e foo://) are equal, in terms of cache-ability. If two requests are equal, then they should use the same cached data. This concerns URL Loading System’s own, built-in caching system, which you’re ignoring for this tutorial. So for this exercise, just rely on the default superclass implementation.

The loading system uses startLoading() and stopLoading() to tell your NSURLProtocol to start and stop handling a request. Your start implementation sets up the NSURLConnection instance to load the data. The stop method exists so that URL loading can be cancelled. This is handled in the above example by cancelling the current connection and getting rid of it.

Woo-hoo! You’ve implemented the interface required of a valid NSURLProtocol instance. Checkout the official documentation describing what methods a valid NSURLProtocol subclass can implement, if you want to read more.

But your coding isn’t done yet! You still need to do the actual work of processing the request, which you do by handling the delegate callbacks from the NSURLConnection you created.

Open MyURLProtocol.swift and add the following methods:

func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
    self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
}
 
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
    self.client!.URLProtocol(self, didLoadData: data)
}
 
func connectionDidFinishLoading(connection: NSURLConnection!) {
    self.client!.URLProtocolDidFinishLoading(self)
}
 
func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
    self.client!.URLProtocol(self, didFailWithError: error)
}

These are all NSURLConnection delegate methods. They are called when the NSURLConnection instance you’re using to load the data has a response, when it has data, when it finishes loading and when it fails. In each of these cases, you’re going to need to hand this information off to the client.

So to recap, your MyURLProtocol handler creates its own NSURLConnection and asks that connection to process the request. In the NSURLConnection delegate callbacks methods above, the protocol handler is relaying messages from the connection back to the URL Loading System. These messages talk about loading progress, completion, and errors.

Look and you’ll see the close family resemblance in message signatures for the NSURLConnectionDelegate and the NSURLProtocolClient — they are both APIs for asynchronous data loading. Also notice how MyURLProtocol uses its client property to send messages back to the URL Loading system.

Build and run the project. When the app opens, enter the same URL and hit Go.

Uh-oh! Your browser isn’t loading anything anymore! If you look at the Debug Navigator while it’s running, you’ll see memory usage is out of control. The console log should show a racing scroll of innumerable requests for the same URL. What could be wrong?

In the console you should see lines being logged forever and ever like this:

Request #0: URL = http://raywenderlich.com/
Request #1: URL = http://raywenderlich.com/
Request #2: URL = http://raywenderlich.com/
Request #3: URL = http://raywenderlich.com/
Request #4: URL = http://raywenderlich.com/
Request #5: URL = http://raywenderlich.com/
Request #6: URL = http://raywenderlich.com/
Request #7: URL = http://raywenderlich.com/
Request #8: URL = http://raywenderlich.com/
Request #9: URL = http://raywenderlich.com/
Request #10: URL = http://raywenderlich.com/
...
Request #1000: URL = http://raywenderlich.com/
Request #1001: URL = http://raywenderlich.com/
...

You’ll need to return to Xcode and stop the app from there before diving into the problem.

Squashing the Infinite Loop with Tags

Think again about the URL Loading System and protocol registration, and you might have a notion about why this is happening. When the UIWebView wants to load the URL, the URL Loading System asks MyURLProtocol if it can handle that specific request. Your class says true, it can handle it.

So the URL Loading System will create an instance of your protocol and call startLoading. Your implementation then creates and fires its NSURLConnection. But this also calls the URL Loading System. Guess what? Since you’re always returning true in the canInitWithRequest(_:) method, it creates another MyURLProtocol instance.

This new instance will lead to the creation of one more, and then one more and then an infinite number of instances. That’s why your app doesn’t load anything! It just keeps allocating more memory, and shows only one URL in the console. The poor browser is stuck in an infinite loop! Your users could be frustrated to the point of inflicting damage on their devices.

Obviously you can’t just always return true in the canInitWithRequest(_:) method. You need to have some sort of control to tell the URL Loading System to handle that request only once. The solution is in the NSURLProtocol interface. Look for the class method called setProperty(_:forKey:inRequest:) that allows you to add custom properties to a given URL request. This way, you can ‘tag’ it by attaching a property to it, and the browser will know if it’s already seen it before.

So here’s how you break the browser out of infinite instance insanity. Open MyURLProtocol.swift. Then change the startLoading() and the canInitWithRequest(_:) methods as follows:

override class func canInitWithRequest(request: NSURLRequest!) -> Bool {
    println("Request #\(requestCount++): URL = \(request.URL.absoluteString)")
 
    if NSURLProtocol.propertyForKey("MyURLProtocolHandledKey", inRequest: request) != nil {
      return false
    }
 
    return true
}
 
override func startLoading() {
    var newRequest = self.request.copy() as NSMutableURLRequest
    NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)
 
    self.connection = NSURLConnection(request: newRequest, delegate: self)
}

Now startLoading() sets the property associated with the key "MyURLProtocolHandledKey" to true for a given request. It means the next time it calls canInitWithRequest(_:) for a given NSURLRequest instance, the protocol can ask if this same property is set.

If it is set, and it’s set to true, then it means that you don’t need to handle that request anymore. The URL Loading System will load the data from the web. Since your MyURLProtocol instance is the delegate for that request, it will receive the callbacks from NSURLConnectionDelegate.

Build and run. When you try it now, the app will successfully display web pages in your web view. Sweet victory! The console should now look something like this:

Request #0: URL = http://raywenderlich.com/
Request #1: URL = http://raywenderlich.com/
Request #2: URL = http://raywenderlich.com/
Request #3: URL = http://raywenderlich.com/
Request #4: URL = http://raywenderlich.com/
Request #5: URL = http://raywenderlich.com/
Request #6: URL = http://raywenderlich.com/
Request #7: URL = http://raywenderlich.com/
Request #8: URL = http://raywenderlich.com/
Request #9: URL = http://www.raywenderlich.com/
Request #10: URL = http://www.raywenderlich.com/
Request #11: URL = http://www.raywenderlich.com/
Request #12: URL = http://raywenderlich.com/
Request #13: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
Request #14: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
Request #15: URL = http://cdn4.raywenderlich.com/wp-content/pluginRse/qvidueeosjts -#h1t6m:l URL = ht5t-pv:i/d/ecodn3.raywenderlich.com/-wppl-acyoenrtent/themes/raywenderlich/-sftoyrl-ew.omridnp.css?vreers=s1/4p0l2u9g6i2n8-4s2t
yles.css?ver=3.9.1
Request #17: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
Request #18: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1
Request #19: URL = http://www.raywenderlich.com/wp-content/plugins/wp-polls/polls-css.css?ver=2.63
Request #20: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
Request #21: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
Request #22: URL = http://cdn4.raywenderlich.com/wp-content/plugins/powerpress/player.min.js?ver=3.9.1
Request #23: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1
Request #24: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1
Request #25: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842
Request #26: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1
...

You might be wondering why you did all of this just to get the app to behave just like it was when you started. Well, because you need to prepare for the fun part! Now you have all the control of the URL data of your app and you can do whatever you want with it. It’s time to start caching your app’s URL data.

Implementing the Local Cache

Remember the basic requirement for this app: for a given request, it should load the data from the web just once, and then cache it. If the same request is fired again in the future, your protocol will serve the cached response without reloading it from the web.

Note: The starter project already includes a basic Core Data model and stack. You don’t need to know the details of Core Data and can just think of it as an opaque data store; if you’re interested, check out Apple’s Core Data Programming Guide.

It’s time to save the responses your app receives from the web, and retrieve them whenever it has matching cached data. Open MyURLProtocol.swift and add the following import to the top of the file:

import CoreData

Next, add two properties inside the class definition:

var mutableData: NSMutableData!
var response: NSURLResponse!

The response property will keep the reference to the metadata you’ll need when saving the response from a server. The mutableData property will be used to hold the data that the connection receives in the connection(_:didReceiveData:) delegate method. Whenever the connection finishes, you can cache the response (data and metadata).

Then add the following method to the class:

func saveCachedResponse () {
    println("Saving cached response")
 
    // 1
    let delegate = UIApplication.sharedApplication().delegate as AppDelegate
    let context = delegate.managedObjectContext!
 
    // 2
    let cachedResponse = NSEntityDescription.insertNewObjectForEntityForName("CachedURLResponse", inManagedObjectContext: context) as NSManagedObject
 
    cachedResponse.setValue(self.mutableData, forKey: "data")
    cachedResponse.setValue(self.request.URL.absoluteString, forKey: "url")
    cachedResponse.setValue(NSDate(), forKey: "timestamp")
    cachedResponse.setValue(self.response.MIMEType, forKey: "mimeType")
    cachedResponse.setValue(self.response.textEncodingName, forKey: "encoding")
 
    // 3
    var error: NSError?
    let success = context.save(&error)
    if !success {
        println("Could not cache the response")
    }
}

Here’s what this method does:

  1. Obtain the Core Data NSManagedObjectContext from the AppDelegate instance. The managed object context is your interface to Core Data.
  2. Create an instance of NSManagedObject to match the data model you saw in the .xcdatamodeld file. Set its properties based on the references to the NSURLResponse and NSMutableData that you kept.
  3. Save the Core Data managed object context.

Now that you have a way to store the data, you need to call this method from somewhere. Still in MyURLProtocol.swift, change the NSURLConnection delegate methods to the following implementations:

func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
    self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
 
    self.response = response
    self.mutableData = NSMutableData()
}
 
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
    self.client!.URLProtocol(self, didLoadData: data)
    self.mutableData.appendData(data)
}
 
func connectionDidFinishLoading(connection: NSURLConnection!) {
    self.client!.URLProtocolDidFinishLoading(self)
    self.saveCachedResponse()
}

Instead of directly handing off to the client, the response and data are stored by your custom protocol class now.

Build and run. Nothing changes in the app’s behavior, but remember that now successfully retrieved responses from the web server save to your app’s local database.

Retrieving the Cached Response

Finally, now it’s time to retrieve cached responses and send them to the NSURLProtocol‘s client. Open MyURLProtocol.swift. Then add the following method:

func cachedResponseForCurrentRequest() -> NSManagedObject? {
    // 1
    let delegate = UIApplication.sharedApplication().delegate as AppDelegate
    let context = delegate.managedObjectContext!
 
    // 2
    let fetchRequest = NSFetchRequest()
    let entity = NSEntityDescription.entityForName("CachedURLResponse", inManagedObjectContext: context)
    fetchRequest.entity = entity
 
    // 3
    let predicate = NSPredicate(format:"url == %@", self.request.URL.absoluteString!)
    fetchRequest.predicate = predicate
 
    // 4
    var error: NSError?
    let possibleResult = context.executeFetchRequest(fetchRequest, error: &error) as Array<NSManagedObject>?
 
    // 5
    if let result = possibleResult {
        if !result.isEmpty {
            return result[0]
        }
    }
 
    return nil
}

Here’s what this does:

  1. Grab the Core Data managed object context, just like in saveCachedResponse().
  2. Create an NSFetchRequest saying that you want to find entities called CachedURLResponse. This is the entity in the managed object model that you want to retrieve.
  3. The predicate for the fetch request needs to obtain the CachedURLResponse object that relates to the URL that you’re trying to load. This code sets that up.
  4. Execute the fetch request is.
  5. If there are any results, return the first result.

Now it’s time to look back at the startLoading() implementation. Rather than just load everything from the web, it needs to check for a cached response for the URL first. Find the current implementation and replace it with the following:

override func startLoading() {
    // 1
    let possibleCachedResponse = self.cachedResponseForCurrentRequest()
    if let cachedResponse = possibleCachedResponse {
        println("Serving response from cache")
 
        // 2
        let data = cachedResponse.valueForKey("data") as NSData!
        let mimeType = cachedResponse.valueForKey("mimeType") as String!
        let encoding = cachedResponse.valueForKey("encoding") as String!
 
        // 3
        let response = NSURLResponse(URL: self.request.URL, MIMEType: mimeType, expectedContentLength: data.length, textEncodingName: encoding)
 
        // 4
        self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
        self.client!.URLProtocol(self, didLoadData: data)
        self.client!.URLProtocolDidFinishLoading(self)
    } else {
        // 5
        println("Serving response from NSURLConnection")
 
        var newRequest = self.request.copy() as NSMutableURLRequest
        NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)
        self.connection = NSURLConnection(request: newRequest, delegate: self)
    }
}

Here’s what that does:

  1. First, you need to find out if there’s a cached response for the current request.
  2. If there is, then pull all the relevant data out of the cached object.
  3. Create an NSURLResponse object from the saved data.
  4. Tell the client about the response and data. You set the client’s cache storage policy to .NotAllowed since you don’t want the client to do any caching of its own since that’s your job. Then you can call URLProtocolDidFinishLoading right away to signal that it has finished loading. No network calls – that’s it!
  5. If there was no cached response, then load the data as usual.

Build and run your project again. Browse a couple of web sites and then quit the app. Switch your device to Airplane mode (or, if using the iOS simulator, turn your computer’s Wi-Fi off / unplug the Ethernet cable) and run it again. Try to load any website you just loaded. It should load the pages from the cached data. Woo hoo! Rejoice! You did it!!!

You should see lots of entries in the console that look like this:

Request #22: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1
Serving response from cache

That’s the log saying that the response is coming from your cache!

And that’s that. Now your app successfully caches retrieved data and metadata from web page requests. Your users will enjoy faster page loads and superior performance! :]

When To Use NSURLProtocol?

How can you use NSURLProtocol to make your app cooler, faster, stronger and jaw-droppingly awesome? Here are a few examples:

Provide Custom Responses For Your Network Requests:

It doesn’t matter if you’re making a request using a UIWebView, NSURLConnection or even using a third-party library (like AFNetworking, MKNetworkKit, your own, etc, as these are all built on top of NSURLConnection). You can provide a custom response, both for metadata and for data. You might use this if you want to stub out the response of a request for testing purposes, for example.

Skip Network Activity and Provide Local Data:

Sometimes you may think it’s unnecessary to fire a network request to provide the app whatever data it needs. NSURLProtocol can set your app up to find data on local storage or in a local database.

Redirect Your Network Requests:

Have you ever wished you could redirect requests to a proxy server — without trusting the user to follow specific iOS setup directions? Well, you can! NSURLProtocol gives you what you want — control over requests. You can set up your app to intercept and redirect them to another server or proxy, or wherever you want to. Talk about control!!

Change the User-agent of Your Requests:

Before firing any network request, you can decide to change its metadata or data. For instance, you may want to change the user-agent. This could be useful if your server changes content based on the user-agent. An example of this would be differences between the content returned for mobile versus desktop, or the client’s language.

Use Your Own Networking Protocol:

You may have your own networking protocol (for instance, something built on top of UDP). You can implement it and, in your application, you still can can keep using any networking library you prefer.

Needless to say, the possibilities are many. It would be impractical (but not impossible) to list all the possibilities you have with NSURLProtocol in this tutorial. You can do anything you need with a given NSURLRequest before it’s fired by changing the designated NSURLResponse. Better yet, just create your own NSURLResponse. You’re the developer, after all.

While NSURLProtocol is powerful, remember that it’s not a networking library. It’s a tool you can use in addition to the library you already use. In short, you can take advantage of NSURLProtocol‘s benefits while you use your own library.

Where To Go From Here

Here is where you can download the final code for this tutorial.

This example covered a simple usage of NSURLProtocol, but don’t mistake it as a complete solution for caching. There is a lot more to implementing a production-quality caching browser. In fact, the loading system has built-in caching configurations, which are worth getting to know. The goal of this tutorial is simply to show you the possibilities. Because NSURLProtocol has access to the data going in and out of so many components, it’s very powerful! There are almost no limits to what you can do implementing the -startLoading method.

While IETF’s RFC 3986 may modestly define URLs as a “…compact sequence of characters that identifies an abstract or physical resource…” the truth is that the URL is its own mini language. It’s the domain-specific language (DSL) for naming and locating things. It’s probably the most pervasive domain-specific language in the world, considering that URLs have crawled out of the screen and are now broadcast in radio and TV advertisements, printed in magazines and splashed on shop signs all over the world.

NSURLProtocol is a language you can use in a myriad of ways. When Twitter wanted to implement the SPDY protocol on iOS, an optimized successor to HTTP 1.1, they did it with NSURLProtocol. What you use it for, is up to you. NSURLProtocol gives you power and flexibility at the same time requires a simple implementation to accomplish your goals.

Please, feel free to leave any questions or suggestions about this tutorial in our forum discussion. It’s right below!

Using NSURLProtocol with Swift is a post from: Ray Wenderlich

The post Using NSURLProtocol with Swift appeared first on Ray Wenderlich.

Cookbook: Making a Calendar Reminder

$
0
0

In this cookbook-style tutorial, you will learn how to access user reminders programmatically.

If you are not already familiar with reminders, consider reading the Apple documentation of the Reminders app. According to Apple, reminders allow you “to organize your life in to-do lists, complete with due dates and locations.”

To get started, download the starter project.

What are you going to work with?

  • EventKit framework
  • UITableView
  • UITableViewController

Authorization

To work with reminder and calendar events, you need to link against EventKit. You will also need a persistent store to save reminder items. Conveniently, EventKit provides this for you: EKEventStore. An EKEventStore allows you to fetch, create, edit, and delete events from a user’s Calendar database.

Both reminders and calendar data are stored in the Calendar database. Ideally, you will have only one event store for your entire app, and you will instantiate it once. That’s because an EKEventStore object requires a relatively large amount of time to initialize and release. It is very inefficient to initialize and release a separate event store for each event-related task. You should use a single event store and continue using it for as long as your app is running!

Calendar database diagram

Calendar database diagram

You can see that reminders and calendars are stored in the same persistent store, but it’s often referred to as just the “calendar database”. In this tutorial, when you see calendars, that really means calendars and reminders, but since programmers are lazy, we just abbreviate it to calendars :]

Before you can access the user’s Calendar you must request access to use the user’s Calendar database. iOS will prompt the user to allow or deny use of calendar information. The most appropriate time to do this is when the app is about to actually access the reminders database.

In the following modification of RWTableViewController.m from the starter project, viewDidLoad ultimately triggers the access request. Note the first line, @import EventKit;, which causes the app to be linked against EventKit and makes that framework’s classes available for use within RWTableViewController.m.

Open RWTableViewController.m and replace the @interface section with the following:

@import EventKit;
 
@interface RWTableViewController ()
 
// The database with calendar events and reminders
@property (strong, nonatomic) EKEventStore *eventStore;
 
// Indicates whether app has access to event store.
@property (nonatomic) BOOL isAccessToEventStoreGranted;
 
// The data source for the table view
@property (strong, nonatomic) NSMutableArray *todoItems;
 
@end

Then add the following two methods:

// 1
- (EKEventStore *)eventStore {
  if (!_eventStore) {
    _eventStore = [[EKEventStore alloc] init];
  }
  return _eventStore;
}
 
- (void)updateAuthorizationStatusToAccessEventStore {
  // 2  
  EKAuthorizationStatus authorizationStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeReminder];
 
  switch (authorizationStatus) {
    // 3
    case EKAuthorizationStatusDenied:
    case EKAuthorizationStatusRestricted: {
      self.isAccessToEventStoreGranted = NO;
      UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Access Denied"
        message:@"This app doesn't have access to your Reminders." delegate:nil
        cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
      [alertView show];
      [self.tableView reloadData];
      break;
    }
 
    // 4
    case EKAuthorizationStatusAuthorized:
      self.isAccessToEventStoreGranted = YES;
      [self.tableView reloadData];
      break;
 
    // 5  
    case EKAuthorizationStatusNotDetermined: {
      __weak RWTableViewController *weakSelf = self;
      [self.eventStore requestAccessToEntityType:EKEntityTypeReminder
                                      completion:^(BOOL granted, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
          weakSelf.isAccessToEventStoreGranted = granted;
          [weakSelf.tableView reloadData];
        });
      }];
      break;
    }
  }
}

Finally, call updateAuthorizationStatusToAccessEventStore from viewDidLoad

[self updateAuthorizationStatusToAccessEventStore];

So what’s going on here?

  1. This is just a basic lazy instantiation method for eventStore.
  2. Ask the system what the current authorization status is for the event store, using EKEventStore's the class method authorizationStatusForEntityType:. You pass it EKEntityTypeReminder as the event type, which indicates you seek permission to access reminders. Then evaluate different scenarios.
  3. EKAuthorizationStatusDenied means that the user explicitly denied access to the service for your application EKAuthorizationStatusRestricted means that your app is not authorized to access the service possibly due to active restrictions such as parental controls. In both of the above cases you can’t do anything, so just display an alert view and inform the user.
  4. EKAuthorizationStatusAuthorized means that your app has access and you can read from or write to the database.
  5. EKAuthorizationStatusNotDetermined means that you haven’t requested access yet, or the user hasn’t made a decision yet. To request access you call requestAccessToEntityType:completion: on your instance of EKEventStore.

    Note that the completion handler of requestAccessToEntityType:completion: does not get called on the main queue, but UI calls (such as reloadData) have to be executed on the main thread. The line that starts dispatch_async ensures that reloadData is executed on the main thread.

Build and run the project. You’ll immediately see an alert, asking the user whether he or she wants to give the app access to his or her reminders.

Requesting access

Note: If you are wondering what would happen if user puts your app to the background, goes to Settings > Privacy > Reminders, and revokes your app’s permission, the answer is that iOS will terminate your app! This approach may sound harsh, but it is the reality.

Adding a reminder item

Assuming that the user has granted access to your app, you can add a new reminder item to the database. For a better user experience, you may want to keep your app’s reminders in a separate list. The following modification of RWTableViewController.m demonstrates this approach.

Add a private property to RWTableViewController.m:

@property (strong, nonatomic) EKCalendar *calendar;

Then lazily instantiate that property:

- (EKCalendar *)calendar {
  if (!_calendar) {
 
    // 1
    NSArray *calendars = [self.eventStore calendarsForEntityType:EKEntityTypeReminder];
 
    // 2
    NSString *calendarTitle = @"UDo!";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title matches %@", calendarTitle];
    NSArray *filtered = [calendars filteredArrayUsingPredicate:predicate];
 
    if ([filtered count]) {
      _calendar = [filtered firstObject];
    } else {
 
      // 3
      _calendar = [EKCalendar calendarForEntityType:EKEntityTypeReminder eventStore:self.eventStore];
      _calendar.title = @"UDo!";
      _calendar.source = self.eventStore.defaultCalendarForNewReminders.source;
 
      // 4
      NSError *calendarErr = nil;
      BOOL calendarSuccess = [self.eventStore saveCalendar:_calendar commit:YES error:&calendarErr];
      if (!calendarSuccess) {
        // Handle error
      }
    }
  }
  return _calendar;
}

If you thought lazy meant “not a lot of work”, well, you’re wrong :] So what does this code do?

  1. First, you look up all available reminder lists.
  2. Then you filter the array by finding all lists called UDo!, and return the first list with that name.
  3. If there aren’t any lists named UDo!, create a new one.
  4. To keep your app’s reminders in a separate list, you need an instance of EKCalendar. You have to instantiate an EKCalendar with a type and event store, and specify its source. The source of a calendar represents the account to which this calendar belongs. For simplicity use the same source that defaultCalendarForNewReminders property on your event store has; defaultCalendarForNewReminders is a convenient readonly property on EKEventStore that returns an EKCalendar object either from the user’s iCloud account or locally from the device based on user’s settings.

Now you need a way to add a new reminder to the event store. Add the following method to RWTableViewController.m:

- (void)addReminderForToDoItem:(NSString *)item {
  // 1
  if (!self.isAccessToEventStoreGranted)
    return;
 
  // 2
  EKReminder *reminder = [EKReminder reminderWithEventStore:self.eventStore];
  reminder.title = item;
  reminder.calendar = self.calendar;
 
  // 3
  NSError *error = nil;
  BOOL success = [self.eventStore saveReminder:reminder commit:YES error:&error];
  if (!success) {
    // Handle error.
  }
 
  // 4
  NSString *message = (success) ? @"Reminder was successfully added!" : @"Failed to add reminder!";
  UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Dismiss", nil];
  [alertView show];
}

Though the code above may seem like it’s a lot, it’s actually pretty straight-forward:

  1. Return early if the user hasn’t given permission to access the event store.
  2. Create the reminder. A reminder item is an instance of EKReminder. At the minimum you have to set two properties on EKReminder: title and calendar.
  3. Store the reminder in the event store and handle the error if this fails
  4. Give the user some feedback in the form of an alert

You have a method to add a reminder, but you still need to call that method. Still in RWTableViewController.m, find the comment // Add the reminder to the store in tableView:cellForRowAtIndexPath:, and replace it with

[self addReminderForToDoItem:object];

Build and run the project. You should be able to add a reminder for to-do items by tapping ‘Add Reminder’ for any to-do item.

Reminder added

Fetching Reminders

While your app is authorized to access the user’s Calendar database, you can fetch either a set of reminders by using predicates or fetch a particular reminder by using its unique identifier, if you have one. The following modification of RWTableViewController.m demonstrates the predicate approach.

First, add another property:

@property (copy, nonatomic) NSArray *reminders;

Create fetchReminders:

- (void)fetchReminders {  
  if (self.isAccessToEventStoreGranted) {    
    // 1
    NSPredicate *predicate =
      [self.eventStore predicateForRemindersInCalendars:@[self.calendar]];
 
    // 2
    [self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {      
      // 3      
      self.reminders = reminders;      
      dispatch_async(dispatch_get_main_queue(), ^{
        // 4
        [self.tableView reloadData];
      });
    }];
  }
}

So what does fetchReminders do?

  1. predicateForRemindersInCalendars: returns a predicate for the calendars that are passed to this method. In your case, it’s just self.calendar.
  2. Here you fetch all the reminders that match the predicate you created in the previous step.
  3. In the completion block you can store the returned array of reminders in a private property.
  4. After all reminders have been fetched, reload the table view (on the main queue, of course!).

Next, add a call to fetchReminders in viewDidLoad, so that when the view is loaded for the first time, all reminders are being loaded immediately. Also register for EKEventStoreChangedNotification in viewDidLoad:

- (void)viewDidLoad {
  // some code...
 
  [self fetchReminders];
  [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(fetchReminders)
    name:EKEventStoreChangedNotification object:nil];
 
  // the rest of the code...
}

And, as a good citizen, you remove yourself as an observer as well of course:

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
}

The EKEventStoreChangedNotification notification is posted whenever changes are made to the calendar database, for example when a reminder is added, deleted, etc. When you receive this notification, you should refetch all EKReminder objects you have because they are considered stale.

Now that you can fetch reminders, it’s time to hide the “Add Reminder” button for to-do items from the table view when a reminder for that to-do item was added.

To do so, create a new method called itemHasReminder::

- (BOOL)itemHasReminder:(NSString *)item {
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title matches %@", item];
  NSArray *filtered = [self.reminders filteredArrayUsingPredicate:predicate];
  return (self.isAccessToEventStoreGranted && [filtered count]);
}

Then modify tableView:cellForRowAtIndexPath: to the following:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  static NSString *kIdentifier = @"Cell Identifier";
 
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kIdentifier forIndexPath:indexPath];
 
  // Update cell content from data source.
  NSString *object = self.todoItems[indexPath.row];
  cell.backgroundColor = [UIColor whiteColor];
  cell.textLabel.text = object;
 
  if (![self itemHasReminder:object]) {
    // Add a button as accessory view that says 'Add Reminder'.
    UIButton *addReminderButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    addReminderButton.frame = CGRectMake(0.0, 0.0, 100.0, 30.0);
    [addReminderButton setTitle:@"Add Reminder" forState:UIControlStateNormal];
 
    [addReminderButton addActionblock:^(UIButton *sender) {
      [self addReminderForToDoItem:object];
    } forControlEvents:UIControlEventTouchUpInside];
 
    cell.accessoryView = addReminderButton;
  } else {
    cell.accessoryView = nil;
  }
 
  return cell;
}

tableView:cellForRowAtIndexPath: remains largely the same, except you wrap it in a conditional statement that checks whether an item already has a reminder and if it does, it doesn’t show the “Add Reminder” button.

Build and run, and add reminders for a few of your to-do items. You’ll see that to-do items that you add reminders for don’t show the “Add Reminder” button, and because of the notification observing, any newly added reminders will make the button for the corresponding to-do item disappear.

Add Reminder button conditionally hidden

You can download the answer here at GitHub.

Deleting a reminder

This is the easiest task so far. The approach here is to modify RWTableViewController.m so that whenever a row gets deleted from the to-do list, any matching reminders are also deleted.

Add deleteReminderForToDoItem: and implement it as follows:

- (void)deleteReminderForToDoItem:(NSString *)item {
  // 1
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title matches %@", item];
  NSArray *results = [self.reminders filteredArrayUsingPredicate:predicate];
 
  // 2
  if ([results count]) {
    [results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
      NSError *error = nil;
      // 3
      BOOL success = [self.eventStore removeReminder:obj commit:NO error:&error];
      if (!success) {
        // Handle delete error
      }
    }];
 
    // 4
    NSError *commitErr = nil;
    BOOL success = [self.eventStore commit:&commitErr];
    if (!success) {
      // Handle commit error.
    }
  }
}
  1. Find the matching EKReminder(s) for the item that was passed in.
  2. Check if there are any matching reminders
  3. Loop over all the matching reminders, and remove them from the event store using removeReminder:commit:error:.
  4. Commit the changes made to the event store. If you have more than one EKReminder to delete, it is better not to commit them one by one. Rather, delete them all and then commit once at the end. This rule also applies when adding new events to the store.

You call this method from tableView:commitEditingStyle:forRowAtIndexPath:. Find that method and replace its implementation:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
 
  NSString *todoItem = self.todoItems[indexPath.row];
 
  // Remove to-do item.
  [self.todoItems removeObject:todoItem];
  [self deleteReminderForToDoItem:todoItem];
 
  [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}

Build and run the project. Now you can delete a user’s reminders from the Calendar database by swiping over a table cell and clicking delete. Of course, this removes the to-do item completely, not just the reminder. When you remove a few reminders, and build and run again, you’ll see the “Add Reminder” button show up again, indicating that that to-do item does not have a reminder yet.

Deleting to-do item

Reminder deleted

Setting a completion and due date

Sometimes it’s useful for reminders to have completion and due dates. Setting a completion date for a reminder is really easy: you just mark the reminder as completed and the completion date gets set for you.

Open RWTableViewController.m again, and implement tableView:didSelectRowAtIndexPath::

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
 
  NSString *toDoItem = self.todoItems[indexPath.row];
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title matches %@", toDoItem];
 
  // Assume there are no duplicates...
  NSArray *results = [self.reminders filteredArrayUsingPredicate:predicate];
  EKReminder *reminder = [results firstObject];
  reminder.completed = !reminder.isCompleted;
 
  NSError *error;
  [self.eventStore saveReminder:reminder commit:YES error:&error];
  if (error) {
    // Handle error
  }
 
  cell.imageView.image = (reminder.isCompleted) ? [UIImage imageNamed:@"checkmarkOn"] : [UIImage imageNamed:@"checkmarkOff"];
}

This code should be pretty self-explanatory. EKReminder has a property completed that you can use to mark a reminder as completed. As soon as you call setCompleted:YES, the completion date is set automatically for you. If you setComplete:NO, completion date will be nilled out.

Build and run. Now you can mark a reminder as completed by simply tapping on a to-do item. Reminder-enabled to-do items will have a checkmark that changes from gray to green based on the completion status of the corresponding reminder.

Completing reminders

The checkmarks only show up when you actually tap an item, but what about reminders that existed before running the app? That’s a little exercise for you. The solution can of course be found below, but try to figure it out yourself first. Only then do you really learn something! Hint: you only have to change tableView:cellForRowAtIndexPath:.

Solution Inside: Solution SelectShow>

You can download the answer here at GitHub.

Setting the due date can be a little bit tricky. Unless you know the exact date and time, you probably need to do some date math. For the purpose of this tutorial, assume you want to set a default due date for all reminders in your app. The default due date is tomorrow at 4:00 P.M.

Date math can be complicated considering leap years, daylight savings, user locale etc. NSCalendar and NSDateComponents are your best bet at such time. Not coincidentally, EKReminder has a property called dueDateComponents, whose type is NSDateComponents.

The starter project came with dateComponentsForDefaultDueDate, which we’re going to leverage in addReminderForToDoItem: by adding a line after reminder.calendar = self.calendar:

reminder.dueDateComponents = [self dateComponentsForDefaultDueDate];

Now all that’s left is showing this due date somewhere. For that, modify tableView:cellForRowAtIndexPath: once again, and add the following code at the end of the else part of the conditional:

if (reminder.dueDateComponents) {
  NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
  NSDate *dueDate = [calendar dateFromComponents:reminder.dueDateComponents];
  cell.detailTextLabel.text = [NSDateFormatter localizedStringFromDate:dueDate dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle];
}

This just takes the dueDateComponents from all to-do items that have a reminder, and convert it into an actual due date which is shown underneath the to-do items. Hit build and run and you’ll see something like this:

Reminder due date

Note: Any previously added reminders don’t have due dates yet, which is why they don’t show a due date. Add a new reminder and you’ll see that it will have a due date set at 4PM tomorrow.

And that’s it. You now have an almost fully functioning to-do app that has access to the user’s calendar and reminders.

Where to Go From Here?

You can download the completed project here.

This was just a quick introduction to the reminders and what a typical interaction with the users’ reminders looks like. EKEventStore of course has support for actual calendar events (such as “meeting with Bob every Wednesday at 2:00 PM”), which this tutorial did not discuss, but can be very useful as well.

I hope you enjoyed this cookbook article. If you would like to see more cookbook-style articles like this in the future or if you have any questions, please leave a comment in the discussion below!

Cookbook: Making a Calendar Reminder is a post from: Ray Wenderlich

The post Cookbook: Making a Calendar Reminder appeared first on Ray Wenderlich.

Video Tutorial: Introduction to Unity Part 6: Materials

Video Tutorial: Introduction to Unity Part 7: Physics

Viewing all 4373 articles
Browse latest View live