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

AsyncDisplayKit Tutorial: Achieving 60 FPS scrolling

$
0
0
Learn how to achieve 60 FPS scrolling with AsyncDisplayKit.

Learn how to achieve 60 FPS scrolling with AsyncDisplayKit.

Facebook’s Paper team has come out with another great library: AsyncDisplayKit. This library enables super-responsive user interfaces by letting you perform image decoding, layout and rendering operations on background threads, so they don’t block user interaction. Learn about it in this AsyncDisplayKit tutorial.

For example, you can use AsyncDisplayKit to build silky-smooth, 60-frame-per-second scrolling experiences for very complex interfaces, where normal UIKit optimizations wouldn’t be enough to overcome performance challenges.

In this tutorial, you’ll take a starter project that has some major UICollectionView scrolling issues and use AsyncDisplayKit to drastically improve its performance. Along the way, you’ll learn how to use AsyncDisplayKit in your existing projects.

Note: Before beginning this tutorial, you should be familiar with Swift, Core Animation and Core Graphics.

Check out the Swift tutorials here on this site, Core Animation Programming Guide and Quartz 2D Programming Guide if you want to brush up or dive into these topics. WWDC 2012’s session named “iOS App Performance: Graphics and Animations” is another great resource that I highly recommend—you can watch it multiple times and learn something new each time!

Getting Started

Before getting started, take a look at AsyncDisplayKit’s intro. This will give you a brief overview of what the library is, and what it solves.

When you’re ready, download the starter project. You’ll need Xcode 6.1 and the iOS 8.1 SDK to build the project.

Note: The code in this tutorial was written for AsyncDisplayKit 1.0. This version of the library is included in the starter project.

The app you’re going to be working with consists of a UICollectionView of cards describing different animals found in a rainforest. Each rainforest info card includes a picture, name and description of a rainforest animal. The background of each card is a blurred version of the main picture. Visual design details on the card ensure the text is legible.

Layers app card view

In Xcode, open Layers.xcworkspace from the downloaded starter project.

Throughout this tutorial, follow these guidelines to see the most dramatic benefit from using AsyncDisplayKit:

  • Run the app on a real device. It will be hard to see performance improvements if you run on the iOS Simulator.
  • The app is universal, but it looks best on an iPad.
  • Finally, to really appreciate what this library can do for you, run the app on the oldest device you can find that runs iOS 8.1. A third-generation iPad is perfect, because it has a retina screen but is not particularly fast.

Once you’ve got your device of choice, build and run the project. You will see something that looks like this:

IMG_0002

Scroll through the collection view and notice the poor frame rate. On a third-generation iPad, it’s about 15-20 FPS. The collection view is dropping a ton of frames. By the end of this tutorial, you’ll have it scrolling at (or extremely close to) 60 FPS!

Note: All the images you see inside the app are bundled in the asset catalog. None of them are retrieved from the network.

Measuring Responsiveness

Before using AsyncDisplayKit on an existing project, you should measure your UI’s performance with Instruments so that you have a baseline to measure against as you make changes.

Most importantly, you want to find out whether you’re CPU-bound or GPU-bound. This means, is it the CPU or the GPU that’s stopping your app from running with a higher frame rate. That information will tell you what AsyncDisplayKit features to leverage to optimize your app.

If you have some time on your hands, watch the WWDC 2012 session mentioned in the Getting Started section and/or time profile the starter project with Instruments using a real device. The scrolling performance is CPU-bound. Can you guess what’s causing the collection view to drop so many frames?

Solution Inside SelectShow>

Preparing the Project for AsyncDisplayKit

Using AsyncDisplayKit on an existing project boils down to replacing view hierarchies and/or layer trees with display node hierarchies. Display nodes are the key tenant of AsyncDisplayKit. They sit on top of views and are thread-safe, meaning work that is traditionally performed on the main thread can now be performed off the main thread. This leaves your main thread free to perform other actions such as handling touch events or in the case of this app, handling scrolling of the collection view.

That means your first step in this tutorial is to remove the view hierarchy.

Removing the View Hierarchy

Open RainforestCardCell.swift and delete all the addSubview(...) calls in awakeFromNib(), so that you have the following:

override func awakeFromNib() {
  super.awakeFromNib()
  contentView.layer.borderColor =
    UIColor(hue: 0, saturation: 0, brightness: 0.85, alpha: 0.2).CGColor
  contentView.layer.borderWidth = 1
}

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

override func layoutSubviews() {
  super.layoutSubviews()
}

Now replace configureCellDisplayWithCardInfo(cardInfo:) with the following:

func configureCellDisplayWithCardInfo(cardInfo: RainforestCardInfo) {
  //MARK: Image Size Section
  let image = UIImage(named: cardInfo.imageName)!
  featureImageSizeOptional = image.size
}

Delete all the view properties from RainforestCardCell, so that the only remaining property is as follows:

class RainforestCardCell: UICollectionViewCell {
  var featureImageSizeOptional: CGSize?
  ...
}

Finally, build and run, and you’ll see a whole lot of nothing:

IMG_0001

Now that the cells are empty, they scroll super-smoothly. Your goal is to keep them scrolling smoothly while adding all the content back with nodes instead of views.

Feel free to profile the app using Instruments’s Core Animation template every step of the way on a real device, to see how your changes affect the frame rate.

Adding a Placeholder

Moving forward in RainforestCardCell.swift, add a CALayer variable stored property called placeholderLayer to RainforestCardCell. The type should be an implicitly unwrapped optional:

class RainforestCardCell: UICollectionViewCell {
  var featureImageSizeOptional: CGSize?
  var placeholderLayer: CALayer!
  ...
}

You need a placeholder because the display will be done asynchronously, and if that takes some time, the user will see empty cells—which isn’t pleasant. It’s the same as when you’re fetching an image from the network and you need to fill the cells with placeholders to let your users know the content isn’t ready yet. Except in this case, you’ll be drawing in the background instead of fetching from the network.

In awakeFromNib(), delete the contentView‘s border setup and create and configure a placeholderLayer. Add it to the cell contentView‘s layer. The method will then look like this:

override func awakeFromNib() {
  super.awakeFromNib()
 
  placeholderLayer = CALayer()
  placeholderLayer.contents = UIImage(named: "cardPlaceholder")!.CGImage
  placeholderLayer.contentsGravity = kCAGravityCenter
  placeholderLayer.contentsScale = UIScreen.mainScreen().scale
  placeholderLayer.backgroundColor = UIColor(hue: 0, saturation: 0, brightness: 0.85, alpha: 1).CGColor
  contentView.layer.addSublayer(placeholderLayer)
}

In layoutSubviews(), you need to lay out the placeholderLayer. Replace the method with the following:

override func layoutSubviews() {
  super.layoutSubviews()
 
  placeholderLayer?.frame = bounds
}

Build and run, and you’re back from the brink of nothingness:

IMG_0003

Plain CALayers which are not backed by a UIView have implicit animations when they change frame by default. That’s why you see the layer scale up when it is laid out. To fix that, add change the implementation of layoutSubviews as follows:

override func layoutSubviews() {
  super.layoutSubviews()
 
  CATransaction.begin()
  CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
  placeholderLayer?.frame = bounds
  CATransaction.commit()
}

Build and run, and you’ll see you’ve solved the problem.

Now the placeholders are sitting still, no longer animating their frames.

Your First Node

The first step in rebuilding the app is to add a background image node to each UICollectionView cell. In this section, you’ll learn how to:

  • Create, lay out and add an image node to a UICollectionView cell;
  • Handle cell reuse with nodes and their layers; and
  • Blur the image node.

But before you do anything else, open Layers-Bridging-Header.h and import AsyncDisplayKit:

#import <AsyncDisplayKit/AsyncDisplayKit.h>

This makes AsyncDisplayKit’s classes available from all the Swift files.

Build to make sure everything compiles OK.

Orientation: Rainforest Collection View Mechanics

Now, let’s take a look at the components of the collection view:

  • The View Controller: The RainforestViewController doesn’t do anything fancy. It simply gets an array of data for all the rainforest cards and implements the data source for the UICollectionView. As a matter of fact, you won’t spend much time in the view controller.
  • The Data Source: You’ll spend most of your time in the cell class, RainforestCardCell. The view controller dequeues each cell and simply hands the rainforest card data to the cell by calling configureCellDisplayWithCardInfo(cardInfo:). The cell then uses the data to configure itself.
  • The Cell: In configureCellDisplayWithCardInfo(cardInfo:), the cell creates, configures, lays out and add nodes to itself. This means every time the view controller dequeues a cell, the cell will create and add to itself a brand new node hierarchy.

    If you were using views instead of nodes, this wouldn’t be the best strategy for performance reasons. But because you can create, configure and lay out nodes asynchronously, and because nodes can draw asynchronously, it turns out this isn’t a problem. The trick is to cancel any ongoing asynchronous activity and to remove old nodes when cells are preparing for reuse.

Note: The strategy this tutorial uses to add nodes to cells is an OK one. It’s a good first step toward mastering AsyncDisplayKit.

However, in production, you would want to use ASRangeController to cache your nodes so that you don’t have to re-create the cell’s node hierarchy on every reuse. ASRangeController is beyond the scope of this introductory tutorial, but for more information, check out the header comments in ASRangeController.h.

One last note: Version 1.1 of AsyncDisplayKit (which has not been released as of this writing, but will be available soon thereafter) includes ASCollectionView. Using ASCollectionView would allow the entire collection view in this app to be handled by display nodes. Instead, in this tutorial, each cell will contain a display node hierarchy. As explained above, this works, but it would be better if the entire collection view were handled by nodes. Roll on ASCollectionView!

OK, it’s time to get your hands dirty.

Adding the Background Image Node

Now you’re going to walk through configuring the cell using nodes, one step at a time.

Open RainforestCardCell.swift and replace configureCellDisplayWithCardInfo(cardInfo:) with the following:

func configureCellDisplayWithCardInfo(cardInfo: RainforestCardInfo) {
  //MARK: Image Size Section
  let image = UIImage(named: cardInfo.imageName)!
  featureImageSizeOptional = image.size
 
  //MARK: Node Creation Section
  let backgroundImageNode = ASImageNode()
  backgroundImageNode.image = image
  backgroundImageNode.contentMode = .ScaleAspectFill
}

This creates and configures an ASImageNode constant called backgroundImageNode.

Note: Make sure to include the //MARK: comments in your code. This will make it easier to follow along.

AsyncDisplayKit ships with several display node classes, including ASImageNode, which you use when you need to display an image. It’s the equivalent of a UIImageView, except that ASImageNode decodes images asynchronously by default.

Add the following code at the end of configureCellDisplayWithCardInfo(cardInfo:):

backgroundImageNode.layerBacked = true

This makes backgroundImageNode a layer-backed node.

Nodes can be backed by either UIView or CALayer instances. You typically use a view-backed node when the node needs to handle events such as touch events. If you don’t need to handle events and simply need to display some content, then use a layer-backed node—it’s not as heavy, so you’ll get a small performance gain.

Because this tutorial’s app doesn’t require event handling, you’ll make all the nodes layer-backed. In the code above, since backgroundImageNode is layer-backed, AsyncDisplayKit will create a CALayer for the contents of the rainforest animal’s image.

Continuing in configureCellDisplayWithCardInfo(cardInfo:) add the following code at the bottom of the method:

//MARK: Node Layout Section
backgroundImageNode.frame = FrameCalculator.frameForContainer(featureImageSize: image.size)

This lays out backgroundImageNode with FrameCalculator.

FrameCalculator is a helper class that encapsulates the cell’s layout, returning frames for each node. Notice that everything is laid out manually, without Auto Layout constraints. If you need to build adaptive layouts or localized-driven layouts, tread carefully here, because you can’t add constraints to nodes.

Next, add the following to the bottom of configureCellDisplayWithCardInfo(cardInfo:):

//MARK: Node Layer and Wrap Up Section
self.contentView.layer.addSublayer(backgroundImageNode.layer)

This adds backgroundImageNode’s layer to the cell contentView’s layer.

As noted, AsyncDisplayKit will create a layer for backgroundImageNode. However, you have to place the node inside of a layer tree for the node to show up onscreen. This node will draw asynchronously, so the contents of the node won’t show up until drawing is complete, even though the layer is in a layer tree.

From a technical perspective, the layer is there the entire time. But the rendering of the image is done asynchronously. The layer initially has no contents (i.e. transparent). Once rendering is complete, the layer’s contents is updated to contain the image contents.

At this point, the cell’s content view’s layer will contain two sublayers: the placeholder and the node’s layer. Only the placeholder will be visible until the node finishes drawing.

Notice that configureCellDisplayWithCardInfo(cardInfo:) gets called every time a cell is dequeued. Every time a cell is recycled, this logic is adding a new sublayer to the cell’s contentView layer. Don’t worry; you’ll address this soon.

Back at the top of RainforestCardCell.swift, add an ASImageNode variable stored property called backgroundImageNode to RainforestCardCell, like so:

class RainforestCardCell: UICollectionViewCell {
  var featureImageSizeOptional: CGSize?
  var placeholderLayer: CALayer!
  var backgroundImageNode: ASImageNode? ///< ADD THIS LINE
  ...
}

You need this property because something has to hold onto the backgroundImageNode reference, or else ARC will release it and nothing will show up. Nodes hold onto their layers, but layers don’t hold onto their nodes—so even though the node’s layer is in a layer tree, you still have to hold onto the node.

At the end of the Node Layer and Wrap Up Section in configureCellDisplayWithCardInfo(cardInfo:), set the cell’s new backgroundImageNode property to the original backgroundImageNode constant:

self.backgroundImageNode = backgroundImageNode

Here’s the complete configureCellDisplayWithCardInfo(cardInfo:) method:

func configureCellDisplayWithCardInfo(cardInfo: RainforestCardInfo) {
  //MARK: Image Size Section
  let image = UIImage(named: cardInfo.imageName)!
  featureImageSizeOptional = image.size
 
  //MARK: Node Creation Section
  let backgroundImageNode = ASImageNode()
  backgroundImageNode.image = image
  backgroundImageNode.contentMode = .ScaleAspectFill
  backgroundImageNode.layerBacked = true
 
  //MARK: Node Layout Section
  backgroundImageNode.frame = FrameCalculator.frameForContainer(featureImageSize: image.size)
 
  //MARK: Node Layer and Wrap Up Section
  self.contentView.layer.addSublayer(backgroundImageNode.layer)
  self.backgroundImageNode = backgroundImageNode
}

Build and run, and watch AsyncDisplayKit asynchronously set the layer’s contents with its image. This allows you to scroll while the CPU is still drawing the contents of the layers in the background.

IMG_0006

If you’re running on an older device, notice how the images pop into place—it’s the popcorn effect, not always welcome! The last section of the tutorial addresses this unpleasant pop and shows you how to make the images fade in nicely, like a rock star.

As discussed earlier, a new node is created each time a cell is reused. This is not ideal because it means that a new layer is added to the cell each time the cell is reused.

If you’d like to see the sublayer pileup that’s happening here, scroll up and down multiple times, then breakpoint and print out the sublayers property of a cell’s content view layer. You should see a lot of layers, which isn’t ideal.

Handling Cell Reuse

Continuing in RainforestCardCell.swift, add a CALayer variable stored property called contentLayer to RainforestCardCell. The property should be an optional type:

class RainforestCardCell: UICollectionViewCell {
  var featureImageSizeOptional: CGSize?
  var placeholderLayer: CALayer!
  var backgroundImageNode: ASImageNode?
  var contentLayer: CALayer? ///< ADD THIS LINE
  ...
}

You’ll end up using this property to remove the old node’s layer from the cell content view’s layer tree. You could simply hold onto the node and access its layer property, but the above solution is more explicit.

Add the following code at the end of configureCellDisplayWithCardInfo(cardInfo:):

self.contentLayer = backgroundImageNode.layer

This assigns backgroundImageNode’s layer to the new contentLayer property.

Then replace the implementation of prepareForReuse() with the following:

override func prepareForReuse() {
  super.prepareForReuse()
  backgroundImageNode?.preventOrCancelDisplay = true
}

Because AsyncDisplayKit can draw nodes asynchronously, nodes let you prevent drawing from starting or cancel any drawing that’s in progress. Whenever you need to prevent or cancel drawing, set preventOrCancelDisplay to true as you do above. In this case, you want to cancel any ongoing drawing activity before the cell gets reused.

Next, add the following to the end of prepareForReuse():

contentLayer?.removeFromSuperlayer()

This removes contentLayer from its superlayer — the contentView’s layer.

Every time a cell is recycled, this code removes the old node’s layer that was added during dequeue, thereby solving the pileup issue. So at all times, you should have at most two sublayers: the placeholder and the node’s layer.

Next add the following code to the end of prepareForReuse():

contentLayer = nil
backgroundImageNode = nil

This ensures that the cell releases these references so ARC can clean up, if needed.

Build and run. This time, there’s no sublayer pileup and all unnecessary drawing gets cancelled.

IMG_0006

It’s time to blur, baby, blur.

asyncscroll

Blurring the Image

To blur the image, you’re going to add an extra step to the image node’s display process.

Continuing inside RainforestCardCell.swift, in configureCellDisplayWithCardInfo(cardInfo:), right after setting backgroundImageNode.layerBacked, add the following code:

backgroundImageNode.imageModificationBlock = { input in
  if input == nil {
    return input
  }
  if let blurredImage = input.applyBlurWithRadius(
    30,
    tintColor: UIColor(white: 0.5, alpha: 0.3),
    saturationDeltaFactor: 1.8,
    maskImage: nil, 
    didCancel:{ return false }) {
      return blurredImage
  } else {
    return image
  }
}

ASImageNode’s imageModificationBlock gives you a chance to process the underlying image before displaying it. It’s a neat feature that allows you to do things like apply filters to image nodes.

In the code above, you use the imageModificationBlock to apply a blur to the cell’s background image. The key point is that the image node will draw its contents and execute this closure in the background, keeping the main thread’s run loop running smoothly. The closure takes in the original UIImage and returns a modified UIImage.

This code uses a UIImage blurring category, released by Apple at WWDC 2013, that uses the Accelerate framework to blur images on the CPU. Because blurring can take a lot of time and memory, this version of the category was modified to include a cancelation mechanism. The blurring method will periodically call the didCancel closure to see whether it should stop.

For now, the above code simply returns false for didCancel. You’ll write the actual didCancel closure further down.

Note: Remember how poorly the collection view scrolled when you ran the app for the first time? The blurring method was clogging the main thread. By moving the blur to the background using AsyncDisplayKit, you are drastically improving the scrolling performance of the collection view. It’s like night and day.

Build and run to see your blur effect:

IMG_0009

Notice how smoothly you can scroll through the collection view.

A blur operation begins in the background when the collection view dequeues a cell. When the user scrolls quickly through the collection view, the collection view reuses each cell multiple times, starting many blur operations. The goal is to cancel an in-progress blur operation when the cell for which it’s working is preparing to be reused.

You’re already canceling the node’s drawing in prepareForReuse(), but once control gets handed over to your image modification closure, it’s your responsibility to respond to the node’s preventOrCancelDisplay flag, which you’ll add now.

Canceling the Blur

To cancel the blur while it’s in progress, you need to implement the didCancel closure of the blur method.

Add a capture list to the imageModificationBlock to capture a weak reference to backgroundImageNode:

backgroundImageNode.imageModificationBlock = { [weak backgroundImageNode] input in
   ...
}

You need the weak reference so as to avoid a retain cycle between the closure and the image node. You’ll use the weak backgroundImageNode to determine if you should cancel the blurring.

It’s time to build the blur cancellation closure. Add the code indicated below to the imageModificationBlock:

backgroundImageNode.imageModificationBlock = { [weak backgroundImageNode] input in
  if input == nil {
    return input
  }
 
  // ADD FROM HERE...
  let didCancelBlur: () -> Bool = {
    var isCancelled = true
    // 1
    if let strongBackgroundImageNode = backgroundImageNode {
      // 2
      let isCancelledClosure = {
        isCancelled = strongBackgroundImageNode.preventOrCancelDisplay
      }
 
      // 3
      if NSThread.isMainThread() {
        isCancelledClosure()
      } else {
        dispatch_sync(dispatch_get_main_queue(), isCancelledClosure)
      }
    }
    return isCancelled
  }
  // ...TO HERE
 
  ...
}

Here’s what this code you just added does:

  1. This grabs a strong reference to backgroundImageNode, ready for us to do some work with it. If backgroundImageNode has gone away by the time this runs, then isCancelled will stay true, and the blur will be canceled. There’s no point continuing a blur when there’s no node to display it in!
  2. Here you wrap the cancellation check in a closure because once a node creates its layer or view, you can only access node properties on the main thread. Since you need to access preventOrCancelDisplay, you have to run the check on the main thread.
  3. Finally, here you make sure that isCancelledClosure is called on the main thread. Either directly if we’re already on the main thread, or through a dispatch_sync if not. It must be a synchronous dispatch because we need the closure to finish and set isCancelled before the didCancelBlur closure can return it.

On the call to applyBlurWithRadius(...), change the argument you pass to the didCancel parameter, replacing the closure that always returns false with the closure you defined above and assigned to the didCancelBlur constant:

if let blurredImage = input.applyBlurWithRadius(
  30,
  tintColor: UIColor(white: 0.5, alpha: 0.3),
  saturationDeltaFactor: 1.8,
  maskImage: nil,
  didCancel: didCancelBlur) {
  ...
}

Build and run. You might not notice much difference, but now any blurs that haven’t finished by the time the cell has gone off the screen will be canceled. This will mean the device is doing less than it was before. You may see a slight performance improvement, especially on slower devices such as the third generation iPad.

IMG_0009

Of course, these aren’t really backgrounds without something in the foreground! Your cards need content. Over the next four sections, you’ll learn how to:

  • Create a container node that draws all of its subnodes into a single CALayer;
  • Build a node hierarchy;
  • Create a custom ASDisplayNode subclass; and
  • Build and lay out node hierarchies in the background.

After you have finished this you will have an app that looked like it did before you added AsyncDisplayKit, but has buttery smooth scrolling.

Rasterized Container Node

Up to now, you’ve been working with a single node inside of a cell. Next, you’ll create a container node that will house all of a card’s content.

Adding a Container Node

Continuing inside RainforestCardCell.swift, in configureCellDisplayWithCardInfo(cardInfo:), after setting backgroundImageNode.imageModificationBlock and before the Node Layout Section, add the following code:

//MARK: Container Node Creation Section
let containerNode = ASDisplayNode()
containerNode.layerBacked = true
containerNode.shouldRasterizeDescendants = true
containerNode.borderColor = UIColor(hue: 0, saturation: 0, brightness: 0.85, alpha: 0.2).CGColor
containerNode.borderWidth = 1

This creates and configure an ASDisplayNode constant called containerNode. Notice the container’s shouldRasterizeDescendants property. This is a hint about how nodes work and an opportunity to make them work better.

As the word “descendants” implies, you can create hierarchies or trees of AsyncDisplayKit nodes, just as you can create hierarchies of Core Animation layers. For instance, if you have a hierarchy of nodes that are all layer-backed, then AsyncDisplayKit will create a separate CALayer for each node, and that layer hierarchy will mirror the node hierarchy.

This should sound familiar: It’s analogous to how the layer hierarchy mirrors the view hierarchy when you’re using regular UIKit. However, this stack of layers has a couple of consequences:

  • First, with asynchronous rendering, you would see each layer show up one by one. As AsyncDisplayKit finishes drawing each layer, it immediately makes the layer display content. So if you have one layer that takes longer to draw than another, then it will display after the other. The user would see the piecemeal assembly of all the layers, a process that’s normally invisible since Core Animation redraws all needed layers at the turn of the run loop before displaying anything.
  • Second, having many layers could cause performance issues. Every CALayer needs a backing store of memory for its pixel bitmap or contents. Also, Core Animation has to send each layer over XPC to the render server. Finally, the render server may need to redraw some of the layers to composite them, for instance when blending layers. In general, more layers means more work for Core Animation. There are many benefits to limiting the number of layers you use.

To address this, AsyncDisplayKit has a handy feature: It allows you to draw a node hierarchy into one single-layer container. That’s exactly what shouldRasterizeDescendants does. When you set this, ASDisplayNode won’t set the layer’s contents until it finishes drawing all of its subnodes.

So in the previous step, setting the container node’s shouldRasterizeDescendants to true has two benefits:

  • It ensures that all the nodes in the card display at once, as with plain old synchronous drawing;
  • And it improves efficiency by rasterizing the stack of layers into a single layer and minimizing future compositing.

The downside is that, since you are flattening all the layers into one bitmap, you cannot later animate the nodes individually.

For more information, check out the shouldRasterizeDescendants header comments in ASDisplayNode.h.

Next, after the Container Node Creation Section, add backgroundImageNode as a subnode of containerNode:

//MARK: Node Hierarchy Section
containerNode.addSubnode(backgroundImageNode)

Note: The order in which you add subnodes matters, just as with subviews and sublayers. A node added before another is displayed below the other.

Replace the first line in the Node Layout Section with this:

//MARK: Node Layout Section
containerNode.frame = FrameCalculator.frameForContainer(featureImageSize: image.size)

Finally, lay out backgroundImageNode using FrameCalculator:

backgroundImageNode.frame = FrameCalculator.frameForBackgroundImage(
  containerBounds: containerNode.bounds)

This sets up the backgroundImageNode to fill the containerNode.

You’re almost there with the new node hierarchy, but first you need to setup the layer hierarchy correctly since the container node is now the root.

Managing the Container Node’s Layer

In the Node Layer and Wrap Up Section, instead of adding backgroundImageNode’s layer to the cell contentView’s layer, add containerNode’s layer:

// Replace the following line...
// self.contentView.layer.addSublayer(backgroundImageNode.layer)
// ...with this line:
self.contentView.layer.addSublayer(containerNode.layer)

Delete the following backgroundImageNode property assignment:

self.backgroundImageNode = backgroundImageNode

Because the cell simply needs to hold onto the container node alone, you’ll end up removing the backgroundImageNode property.

Instead of setting the cell’s contentLayer property to backgroundImageNode’s layer, set it to containerNode’s layer:

// Replace the following line...
// self.contentLayer = backgroundImageNode.layer
// ...with this line:
self.contentLayer = containerNode.layer

Add an optional ASDisplayNode variable stored property called containerNode to RainforestCardCell:

class RainforestCardCell: UICollectionViewCell {
  var featureImageSizeOptional: CGSize?
  var placeholderLayer: CALayer!
  var backgroundImageNode: ASImageNode?
  var contentLayer: CALayer?
  var containerNode: ASDisplayNode? ///< ADD THIS LINE
  ...
}

Remember that you need to hold onto your nodes if you don’t them to deallocate them immediately.

Back in configureCellDisplayWithCardInfo(cardInfo:), at the end of the Node Layer and Wrap Up Section, assign the containerNode property to the containerNode constant:

self.containerNode = containerNode

Build and run. The blurred images will display once again! But there’s one final thing to change now that there is a new node hierarchy. Recall that during cell reuse you made the image node stop displaying. Now you need to make the entire node hierarchy stop displaying.

Handling Cell Reuse With the New Node Hierarchy

Staying in RainforestCardCell.swift, in prepareForReuse(), instead of setting backgroundImageNode.preventOrCancelDisplay, call recursiveSetPreventOrCancelDisplay(...) on containerNode and pass true:

override func prepareForReuse() {
  super.prepareForReuse()
 
  // Replace this line...
  // backgroundImageNode?.preventOrCancelDisplay = true
  // ...with this line:
  containerNode?.recursiveSetPreventOrCancelDisplay(true)
 
  contentLayer?.removeFromSuperlayer()
  ...
}

Use recursiveSetPreventOrCancelDisplay() when you need to cancel drawing for an entire node hierarchy. This method will set the preventOrCancelDisplay property of the node and all of its children to either true or false.

Next, still in prepareForReuse(), replace the line that sets backgroundImageNode to nil with a line that sets containerNode to nil:

override func prepareForReuse() {
  ...
  contentLayer = nil
 
  // Replace this line...
  // backgroundImageNode = nil
  // ...with this line:
  containerNode = nil
}

Remove RainforestCardCell’s backgroundImageNode stored property:

class RainforestCardCell: UICollectionViewCell {
  var featureImageSizeOptional: CGSize?
  var placeholderLayer: CALayer!
  // var backgroundImageNode: ASImageNode? ///< REMOVE THIS LINE
  var contentLayer: CALayer?
  var containerNode: ASDisplayNode?
  ...
}

Build and run. The app will function as before, but now you have the image node inside a container node and reuse working as it should.

IMG_0009

Cell Contents

So far you have a node hierarchy, but there’s just one node in the container — the image node. Now it’s time to set up the node hierarchy to replicate the view hierarchy that you had before adding AsyncDisplayKit. This mean adding the text and the non-blurred, feature, image.

Adding the Feature Image

Here you’re going to add the feature image, which is the non-blurred image that’s displayed at the top of the card.

Open RainforestCardCell.swift and find configureCellDisplayWithCardInfo(cardInfo:). At the bottom of the Node Creation Section add the following code:

let featureImageNode = ASImageNode()
featureImageNode.layerBacked = true
featureImageNode.contentMode = .ScaleAspectFit
featureImageNode.image = image

This creates and configures a ASImageNode constant called featureImageNode. This is set up to be layer backed, scale to fit, and set to display the image, unblurred this time.

At the end of the Node Hierarchy Section, add featureImageNode to containerNode as a subnode:

containerNode.addSubnode(featureImageNode)

You’re starting to fill the container with more nodes!

In the Node Layout Section, lay out featureImageNode using FrameCalculator:

featureImageNode.frame = FrameCalculator.frameForFeatureImage(
  featureImageSize: image.size,
  containerFrameWidth: containerNode.frame.size.width)

Build and run. You’ll now see the feature image at the top of the card, on top of the blurred image. Notice how both the feature image and blurred image pop in at the same time. This is your shouldRasterizeDescendants in action which you added earlier.

IMG_0015

Adding the Title Text

Next up is adding the text labels, to display the name of the animal and the description. First up, the name of the animal.

Staying in configureCellDisplayWithCardInfo(cardInfo:), find the Node Creation Section. Add the following code at the end of this section, just after the creation of featureImageNode:

let titleTextNode = ASTextNode()
titleTextNode.layerBacked = true
titleTextNode.backgroundColor = UIColor.clearColor()
titleTextNode.attributedString = NSAttributedString.attributedStringForTitleText(cardInfo.name)

This creates and configures an ASTextNode constant called titleTextNode.

ASTextNode is another node subclass that ships with AsyncDisplayKit and which you use to display text. It’s a node based UILabel effectively. It takes an attributed string, is backed by TextKit and has a lot of features like text links. To learn more about what you can do with this node subclass, head over to ASTextNode.h.

The starter project includes an extension on NSAttributedString that provides factory methods to generate attributed strings for the title and description text that show up in the rainforest info card. The code above uses attributedStringForTitleText(...) from this extension.

Now, at the end of the Node Hierarchy Section, add the following code:

containerNode.addSubnode(titleTextNode)

This adds the titleTextNode to the node hierarchy. It will be on top of both the feature image and the background image since it’s added after them.

At the end of the Node Layout Section add the following code:

titleTextNode.frame = FrameCalculator.frameForTitleText(
  containerBounds: containerNode.bounds,
  featureImageFrame: featureImageNode.frame)

This lays out titleTextNode with FrameCalculator, just as you did with backgroundImageNode and featureImageNode.

Build and run. You now have the title being displayed on top of the feature image. Once again, the label is only rendered once the entire cell is ready to render.

IMG_0017

Adding the Description Text

Adding a node with description text is much like adding a node with title text.

Back in configureCellDisplayWithCardInfo(cardInfo:), at the end of Node Creation Section, add the following code. Add it just after the creation of titleTextNode which you added previously.

let descriptionTextNode = ASTextNode()
descriptionTextNode.layerBacked = true
descriptionTextNode.backgroundColor = UIColor.clearColor()
descriptionTextNode.attributedString = 
  NSAttributedString.attributedStringForDescriptionText(cardInfo.description)

This creates and configures an ASTextNode constant called descriptionTextNode.

At the end of the Node Hierarchy Section, add descriptionTextNode to containerNode:

containerNode.addSubnode(descriptionTextNode)

At the end of the Node Layout Section, lay out descriptionTextNode using FrameCalculator:

descriptionTextNode.frame = FrameCalculator.frameForDescriptionText(
  containerBounds: containerNode.bounds,
  featureImageFrame: featureImageNode.frame)

Build and run. You’ll now see the description text as well.

IMG_0018

Custom Node Subclasses

So far you’ve made use of ASImageNode and ASTextNode. These will take you a long way, but sometimes you need your own nodes just like you sometimes need your own views in traditional UIKit programming.

Creating a Gradient Node Class

Next up, you’ll use the Core Graphics code that’s in GradientView.swift to build a custom gradient display node. This will be used to create a custom node that draws a gradient. The gradient will be displayed at the bottom of the feature image to make the title stand out more.

Open Layers-Bridging-Header.h and add the following code:

#import <AsyncDisplayKit/_ASDisplayLayer.h>

This step is necessary because this class isn’t included in the library’s umbrella header. You need access to this class when subclassing any ASDisplayNode class and when subclassing _ASDisplayLayer.

Click File\New\File…. Select iOS\Source\Cocoa Touch Class. Call the class GradientNode and make it a subclass of ASDisplayNode. Select Swift as the language and then click Next. Save the file and then open GradientNode.swift.

Add the following method to the class:

class func drawRect(bounds: CGRect, withParameters parameters: NSObjectProtocol!,
    isCancelled isCancelledBlock: asdisplaynode_iscancelled_block_t!, isRasterizing: Bool) {
 
}

Just as with UIView or CALayer, you can subclass ASDisplayNode to do custom drawing. You can then use that drawing code to draw into either a UIView’s layer or a stand-alone CALayer, depending on how the client code configures the node. Check out ASDisplayNode+Subclasses.h for more information about subclassing ASDisplayNode.

In addition, the ASDisplayNode‘s drawing method takes more parameters than those on UIView and CALayer, offering you ways to write the method to do less work and be more efficient.

To fill your custom display nodes with content, you need to implement either drawRect(...) or displayWithParameters(...) from the _ASDisplayLayerDelegate protocol. Before continuing, head over to _ASDisplayLayer.h for information about these methods and their parameters; search for _ASDisplayLayerDelegate. Pay close attention to the header comments for drawRect(...).

Because the gradient that sits on top of the feature image is drawn using Core Graphics, you need to use drawRect(...) for this custom node.

Open GradientView.swift and copy the contents of drawRect(...) into GradientNode’s drawRect(...) in GradientNode.swift. The method will look like this:

class func drawRect(bounds: CGRect, withParameters parameters: NSObjectProtocol!,
    isCancelled isCancelledBlock: asdisplaynode_iscancelled_block_t!, isRasterizing: Bool) {
  let myContext = UIGraphicsGetCurrentContext()
  CGContextSaveGState(myContext)
  CGContextClipToRect(myContext, bounds)
 
  let componentCount: UInt = 2
  let locations: [CGFloat] = [0.0, 1.0]
  let components: [CGFloat] = [0.0, 0.0, 0.0, 1.0,
    0.0, 0.0, 0.0, 0.0]
  let myColorSpace = CGColorSpaceCreateDeviceRGB()
  let myGradient = CGGradientCreateWithColorComponents(myColorSpace, components,
    locations, componentCount)
 
  let myStartPoint = CGPoint(x: bounds.midX, y: bounds.maxY)
  let myEndPoint = CGPoint(x: bounds.midX, y: bounds.midY)
  CGContextDrawLinearGradient(myContext, myGradient, myStartPoint,
    myEndPoint, UInt32(kCGGradientDrawsAfterEndLocation))
 
  CGContextRestoreGState(myContext)
}

Now delete GradientView.swift, and build to make sure everything compiles.

Adding the Gradient Node

Open RainforestCardCell.swift and find configureCellDisplayWithCardInfo(cardInfo:). At the bottom of the Node Creation Section, add the following code, just after the creation of descriptionTextNode that you added earlier:

let gradientNode = GradientNode()
gradientNode.opaque = false
gradientNode.layerBacked = true

This creates and configures a GradientNode constant called gradientNode.

In the Node Hierarchy Section, right below the line that adds featureImageNode, add gradientNode to containerNode:

//MARK: Node Hierarchy Section
containerNode.addSubnode(backgroundImageNode)
containerNode.addSubnode(featureImageNode)
containerNode.addSubnode(gradientNode) ///< ADD THIS LINE
containerNode.addSubnode(titleTextNode)
containerNode.addSubnode(descriptionTextNode)

The gradient node needs to go here so that it’s above the feature image, but below the title.

Then add the following code at the bottom of the Node Layout Section:

gradientNode.frame = FrameCalculator.frameForGradient(
  featureImageFrame: featureImageNode.frame)

Build and run. You’ll now see the gradient at the bottom of the feature image. The title certainly stands out more now!

IMG_0019

The Popcorn Effect

As mentioned before, the cell’s node contents “pop” in when it’s finished drawing. This isn’t ideal. So let’s go ahead and fix that now. First up, some more diving into AsyncDisplayKit and how it works.

In the Container Node Creation Section within configureCellDisplayWithCardInfo(cardInfo:), turn off shouldRasterizeDescendants for the container node:

containerNode.shouldRasterizeDescendants = false

Build and run. You’ll notice that now the different nodes in the container hierarchy pop in one by one. You’ll see the text pop in, then the feature image, then the blurred background image.

With shouldRasterizeDescendants turned off, instead of drawing into one container layer, AsyncDisplayKit creates a layer tree that mirrors the info card’s node hierarchy. Remember that the popcorn effect exists because each layer shows up immediately after it’s drawn and some layers take longer to draw than others.

That’s not what we want, but it illustrates how AsyncDisplayKit works. We don’t want that behaviour, so turn shouldRasterizeDescendants back on:

containerNode.shouldRasterizeDescendants = true

Build and run again. Back to the entire cell popping in once it’s finished rendering.

Time to rethink how to get rid of this popcorn effect. First up, let’s take a look at how nodes can be constructed in the background.

Constructing Nodes in the Background

In addition to drawing asynchronously, with AsyncDisplayKit, you can also create, configure and lay out nodes asynchronously. Take a deep breath, because that’s what you’re going to do next.

Creating a Node Construction Operation

You’re going to wrap up the construction of the node hierarchy into an NSOperation. This is a nice way to do it, since that operation can then be easily performed on any operation queue, including a background queue.

Open RainforestCardCell.swift. Then add the following method to the class:

func nodeConstructionOperationWithCardInfo(cardInfo: RainforestCardInfo, image: UIImage) -> NSOperation {
  let nodeConstructionOperation = NSBlockOperation()
  nodeConstructionOperation.addExecutionBlock { 
    // TODO: Add node hierarchy construction
  }
  return nodeConstructionOperation
}

Drawing isn’t the only thing that can slow down the main thread. For complex screens, layouts can sometimes be expensive to compute. So far, with the current state of the tutorial project, a slow node layout causes the collection view to drop frames.

60 FPS means you have ~17 ms to get your cells ready to display, otherwise one or more frames will be dropped. It’s very common with table views and collection views which have complex cells, for frames to be dropped while scrolling because of this.

AsyncDisplayKit to the rescue!

You’ll use the nodeConstructionOperation above to move all the node hierarchy construction and layout out of the main thread and onto a background NSOperationQueue, further ensuring that the collection view will scroll as close to 60 FPS as possible.

Warning: You can access and set node properties in the background, but only before the node’s layer or view has been created, which happens when you access the node’s layer or view property for the first time.

Once the node’s layer or view has been created, you must access and set node properties on the main thread, because the node will forward those calls to its layer or view. If you get a crash with a log that says, ‘Incorrect display node thread affinity,’ this means you are attempting to get/set a node property in the background after its layer or view has been created.

Change the contents of the nodeConstructionOperation operation block to the following:

nodeConstructionOperation.addExecutionBlock {
  [weak self, unowned nodeConstructionOperation] in
  if nodeConstructionOperation.cancelled {
    return
  }
  if let strongSelf = self {
    // TODO: Add node hierarchy construction
  }
}

By the time this operation runs, the cell may have been deallocated. In that case, there’s no need to do any work. Similarly, if the operation is canceled, then there is no work to do either.

An unowned reference to nodeConstructionOperation is required to avoid a retain cycle between the operation and the execution closure.

Now find configureCellDisplayWithCardInfo(cardInfo:). Move everything after the Image Size Section in configureCellDisplayWithCardInfo(cardInfo:) into nodeConstructionOperation’s execution closure. Place the code inside the strongSelf optional binding statement, where the TODO is. The configureCellDisplayWithCardInfo(cardInfo:) method will now look like this:

func configureCellDisplayWithCardInfo(cardInfo: RainforestCardInfo) {
  //MARK: Image Size Section
  let image = UIImage(named: cardInfo.imageName)!
  featureImageSizeOptional = image.size
}

You’ll have a few compiler errors now. This is because self in the operation block is a weak reference and therefore an optional. But you have a strong reference to self because the code is inside the optional binding statement. So replace the lines where the errors are with the following:

strongSelf.contentView.layer.addSublayer(containerNode.layer)
strongSelf.contentLayer = containerNode.layer
strongSelf.containerNode = containerNode

Finally, add the following code underneath those three lines you just changed:

containerNode.setNeedsDisplay()

Build to make sure everything compiles. If you run it right now, then only the placeholder will display, because the node creation operation is not actually being used yet. Let’s now add that.

Using the Node Creation Operation

Open RainforestCardCell.swift and add the following property to the class:

class RainforestCardCell: UICollectionViewCell {
  var featureImageSizeOptional: CGSize?
  var placeholderLayer: CALayer!
  var backgroundImageNode: ASImageNode?
  var contentLayer: CALayer?
  var containerNode: ASDisplayNode?
  var nodeConstructionOperation: NSOperation? ///< ADD THIS LINE
  ...
}

This adds an NSOperation optional variable stored property called nodeConstructionOperation.

You’ll use this property to cancel node construction when the cell is preparing to be recycled. This can happen when the user scrolls very quickly through the collection view, especially if it takes a while to calculate the layout.

In prepareForReuse() add the code indicated below:

override func prepareForReuse() {
  super.prepareForReuse()
 
  // ADD FROM HERE...
  if let operation = nodeConstructionOperation {
    operation.cancel()
  }
  // ...TO HERE
 
  containerNode?.recursiveSetPreventOrCancelDisplay(true)
  contentLayer?.removeFromSuperlayer()
  contentLayer = nil
  containerNode = nil
}

This cancels the operation when the cell is reused, so that if the node creation hasn’t been done yet, it won’t be done.

Now find configureCellDisplayWithCardInfo(cardInfo:) and add the code indicated below:

func configureCellDisplayWithCardInfo(cardInfo: RainforestCardInfo) {
  // ADD FROM HERE...
  if let oldNodeConstructionOperation = nodeConstructionOperation {
    oldNodeConstructionOperation.cancel()
  }
  // ...TO HERE
 
  //MARK: Image Size Section
  let image = UIImage(named: cardInfo.imageName)!
  featureImageSizeOptional = image.size
}

The cell now cancels any in-flight node construction operations when it prepares for reuse and when it starts configuring. This makes sure that the operation is canceled even if the cell is reconfigured before it’s been made ready for reuse.

Build to make sure everything compiles.

Running on the Main Thread

AsyncDisplayKit allows you to do lots of work off the main thread. But when it comes to interfacing with UIKit and CoreAnimation, you need to do all of this on the main thread still. So far, you’ve moved all node creation off the main thread. But there’s one thing that needs to be put back on the main thread — the setting up of the CoreAnimation layer hierarchy.

In RainforestCardCell.swift, go back to nodeConstructionOperationWithCardInfo(cardInfo:image:) and replace the Node Layer and Wrap Up Section with the following:

// 1
dispatch_async(dispatch_get_main_queue()) { [weak nodeConstructionOperation] in
  if let strongNodeConstructionOperation = nodeConstructionOperation {
    // 2
    if strongNodeConstructionOperation.cancelled {
      return
    }
 
    // 3
    if strongSelf.nodeConstructionOperation !== strongNodeConstructionOperation {
      return
    }
 
    // 4
    if containerNode.preventOrCancelDisplay {
      return
    }
 
    // 5
    //MARK: Node Layer and Wrap Up Section
    strongSelf.contentView.layer.addSublayer(containerNode.layer)
    containerNode.setNeedsDisplay()
    strongSelf.contentLayer = containerNode.layer
    strongSelf.containerNode = containerNode
  }
}

Here’s what that does:

  1. Recall that layers are created when the node’s layer property is accessed for the first time. This is why you must run the Node Layer and Wrap Up Section on the main queue, since the code accesses the node’s layer.
  2. The operation is checked to see if it’s canceled before actually adding the layer to the hierarchy. It could be that the cell has been reused or reconfigured before the operation has finished, in which case you don’t want to actually add the layer.
  3. As a safeguard, there is an identity check to make sure the cell’s current nodeConstructionOperation is the same NSOperation instance that dispatched this closure.
  4. Fast return if containerNode’s preventOrCancel is true. If the construction operation finishes, but the node’s draw has been cancelled, you still don’t want the node’s layer to show up in the cell.
  5. Finally, add the node’s layer to the hierarchy, which will create the layer if required.

Build to make sure everything compiles.

Starting the Node Creation Operation

You’ve still not actually created and started the operation. Let’s do that now.

Continuing in RainforestCardCell.swift, change the method signature of configureCellDisplayWithCardInfo(cardInfo:) to the following:

func configureCellDisplayWithCardInfo(
  cardInfo: RainforestCardInfo,
  nodeConstructionQueue: NSOperationQueue)

This adds a new parameter, nodeConstructionQueue. This is an NSOperationQueue which will be used to enqueue a node creation operation.

At the end of configureCellDisplayWithCardInfo(cardInfo:nodeConstructionQueue:), add the following code:

let newNodeConstructionOperation = nodeConstructionOperationWithCardInfo(cardInfo, image: image)
nodeConstructionOperation = newNodeConstructionOperation
nodeConstructionQueue.addOperation(newNodeConstructionOperation)

This creates a node construction operation, assigns it to the cell’s nodeConstructionOperation property, and adds it to the passed-in queue.

Finally, open RainforestViewController.swift. Add an initialized constant stored property called nodeConstructionQueue to RainforestViewController, like so:

class RainforestViewController: UICollectionViewController {
  let rainforestCardsInfo = getAllCardInfo()
  let nodeConstructionQueue = NSOperationQueue() ///< ADD THIS LINE
  ...
}

Next, in collectionView(collectionView:cellForItemAtIndexPath indexPath:), pass the view controller’s nodeConstructionQueue into configureCellDisplayWithCardInfo(cardInfo:nodeConstructionQueue:):

cell.configureCellDisplayWithCardInfo(cardInfo, nodeConstructionQueue: nodeConstructionQueue)

The cell will now create a new node construction operation and add it to the view controller’s operation queue to run concurrently. Remember that a new node hierarchy gets created every time a cell is dequeued. It’s not ideal, but it’s good enough. Check out ASRangeController if you’d like to cache the nodes to be reused.

Whew, OK, now build and run! You’ll see the same as you did before, but now layout and rendering are being performed off the main thread. Neat! I bet you never thought you’d ever see a day where you could do that! This is the power of AsyncDisplayKit. You can move more and more things off the main thread that don’t need to be there. This leaves the main thread free to handle user interaction, keeping your app’s interaction buttery smooth.

IMG_0019

Fading in the Cells

Now for the fun stuff. In this short section, you’ll learn how to:

  • Back nodes with custom display layer subclasses;
  • Leverage implicit animations to animate node layers.

This will enable you to remove the popcorn effect and finally bring a nice fade animation to the party.

Creating a New Layer Subclass

Click File\New\File…. Select iOS\Source\Cocoa Touch Class and click Next. Call the class AnimatedContentsDisplayLayer and make it a subclass of _ASDisplayLayer. Choose Swift as the language and click Next. Finally, save it and open AnimatedContentsDisplayLayer.swift.

Now add the following method to the class:

override func actionForKey(event: String!) -> CAAction! {
  if let action = super.actionForKey(event) {
    return action
  }
 
  if event == "contents" && contents == nil {
    let transition = CATransition()
    transition.duration = 0.6
    transition.type = kCATransitionFade
    return transition
  }
 
  return nil
}

Layers have a contents property which tells the system what to draw for that layer. AsyncDisplayKit works by rendering the contents in the background before finally setting the contents on the main thread.

This code will add a transition animation so that the contents fade into view. You can find more information about implicit layer animations and CAAction in Apple’s Core Animation Programming Guide.

Build to make sure everything compiles.

Fading in the Container Node

You’ve made a layer that will fade in its contents when its set, but now you need to use that layer.

Open RainforestCardCell.swift. Inside nodeConstructionOperationWithCardInfo(cardInfo:image:), at the beginning of the Container Node Creation Section, change the following line:

// REPLACE THIS LINE...
// let containerNode = ASDisplayNode()
// ...WITH THIS LINE:
let containerNode = ASDisplayNode(layerClass: AnimatedContentsDisplayLayer.self)

This tells the container node to use an AnimatedContentsDisplayLayer instance for its backing layer, therefore opting-in to the fade-in animation that you added.

Note: Only subclasses of _ASDisplayLayer can be drawn asynchronously.

Build and run. You’ll now see the container node fading in once it’s drawn.

IMG_0023

Where to Go from Here?

Congratulations! You now have another tool at your disposal for when you need to build high-performing, scrolling user interfaces.

In this tutorial, you took a poorly-performing collection view and significantly improved its scrolling by replacing the view hierarchy with a rasterized AsyncDisplayKit node hierarchy. Exciting!

This is just one example of what you can do. AsyncDisplayKit holds out the promise of reaching levels of UI performance that would be difficult or impossible to achieve via optimizations within ordinary UIKit.

Realistically, to make the most of AsyncDisplayKit, you need a solid understanding of where the true performance bottlenecks are in the standard UIKit. One great thing about AsyncDisplayKit is it provokes us to probe those issues and think about how fast and responsive it is physically possible for our apps to be.

AsyncDisplayKit is a powerful tool for exploring this performance frontier. Use it wisely. And welcome to the cutting edge—the bleeding edge?—of super-responsive UIs.

This is only the beginning for AsyncDisplayKit! The authors and contributors are building new exciting features every day. Keep an eye out for ASCollectionView and ASMultiplexImageNode in version 1.1. Straight from the header, “ASMultiplexImageNode is an image node that can load and display multiple versions of an image. For example, it can display a low-resolution version of an image while the high-resolution version is loading.” Pretty cool :]

You can download the final Xcode project here.

The AsyncDisplayKit guide is here and the AsyncDisplayKit Github repo is here.

The library authors are looking for API design feedback. Make sure to share your thoughts in the Paper Engineering Community group on Facebook or even better you can get involved with the development of AsyncDisplayKit by contributing through pull requests on GitHub.

AsyncDisplayKit Tutorial: Achieving 60 FPS scrolling is a post from: Ray Wenderlich

The post AsyncDisplayKit Tutorial: Achieving 60 FPS scrolling appeared first on Ray Wenderlich.


WatchKit Tutorial with Swift: Getting Started

$
0
0
Get started with WatchKit!

Get started with WatchKit!

iOS developers rejoice – WatchKit is finally here!

WatchKit is Apple’s new framework and related technologies that allow you to create apps for the Apple Watch, released along with Xcode 6.2-beta.

In this WatchKit tutorial, you’ll create your first WatchKit app with Swift. Specifically, you’ll take a Bitcoin price tracking app, and make a Watch app that goes along with it.

In the process, you’ll learn learn about how WatchKit apps are architected, how some of the new WatchKit-specific UI controls work, WatchKit layout, and much more.

Let’s get started! ┗(°0°)┛


Note: Since the Apple Watch SDK docs are open to the public and we haven’t heard anything to the contrary, we are assuming it is OK to talk about WatchKit at this point. If anyone hears an official word on the matter, please let us know!

Getting Started

First, download the starter project for this WatchKit tutorial.

Open the starter project and select the iPhone 6 simulator target. Build and run the app to get a feel for it. The app (by team member Mic Pringle) contacts the BitcoinAverage Price Index API to get the latest Bitcoin price and displays it on the screen.

Are you a Bitcoin billionaire yet?

Are you a Bitcoin billionaire yet?

It’s time to get started with tracking your Bitcoin fortune on the Watch!

In Xcode, navigate to File\New\Target… and select the iOS\Apple Watch\Watch App template.

watch-target

A Watch app is bundled with the main iOS app in the same way extensions work, so this option will create a separate target for you. Click Next to continue.

On the following screen, Xcode will fill in many of the values and you won’t be able to change things such as the Product Name.

watch-targetoptions

Make sure the language is set to Swift and both Include Notification Scene and Include Glance Scene are not checked. Click Finish and Xcode will set up the target and some starter template files for the Watch interface, ready for you to customize!

If you look at the project navigator, you’ll see there are two separate groups:

watch-groups

  1. The Watch App group contains just the storyboard and image assets…and no code! Think of this as the “view” of your app.
  2. The WatchKit Extension contains the code that is executed upon events like the app launching, button taps, or a switch value change. Think of this as the “controller and model” of your app.

WatchKit_03

Until we get “native” Watch apps, the best way to think of this setup is the Watch as a second smaller screen controlled from the extension. In fact, the simulator treats the Watch as just another external display, as you’ll see as you start to add and test the interface objects. ;]

Note: The terminology for Watch apps is a little different from iOS and Mac apps – rather than views, controls and view controllers, you have interfaces, interface objects and interface controllers.

Watch Interface

When designing your Watch app interface, you use storyboards and Interface Builder just as you would for an iOS app. To see this, open the BitWatch Watch App group and select Interface.storyboard.

watch-storyboard1

You’ll see a single empty Interface Controller in the storyboard. Let’s add some controls to it!

Start by dragging a Label from the object library to the interface. Then, drag a Button underneath it.

watch-storyboard2

You might notice something strange here – you can drag the interface elements around on the storyboard but that only changes their position vertically (not horizontally), and locks the controls vertically one after another.

In other words, you can put the label on top of the button or the button on top of the label, but you can’t drag things around “freehand” as you can on iOS. Those of you used to Auto Layout in iOS might feel like this guy:

ConfusedGuy

It turns out Auto Layout is no more on the Apple Watch – it uses its own system for layout and positioning. Let’s take a look.

Positioning

It’s not quite Auto Layout, but every interface element in your Watch app has to be “pinned” to something, whether that’s a screen edge or another object. This will determine its final position.

By default, your two objects are positioned at the top; you can change their order to have one on top of the other, but their position will be relative to the top of the screen.

You can tweak the positioning of your interface elements from the Attributes inspector in the Utilities panel. Select the label and change its Horizontal position to Center, and leave its Vertical position as Top.

watch-centertop

Next, select the button and change its Horizontal position to Center, and its Vertical position as Bottom.

watch-storyboard3

In Auto Layout terms, you can think of this as pinning items to the top and bottom layout guides. Since the watch screen is so much smaller, it’s as easy as setting two position choices – horizontal and vertical. You’ll see how easy it is to get objects to position themselves relative to each other as you add more things to the interface later.

Formatting

First, let’s set some text and formatting to get the interface looking good and explore some of the other options in the Attributes inspector.

Select the label and make sure the Attributes inspector in the Utilities is open. This label will show the Bitcoin price, so set the text to $0.00 and select center alignment. Open the font pop-up by clicking the “T” icon in the Font field, and set the values to Custom, Avenir Next, Demi Bold, Size 30.

watch-labelfont

Select the button and change its title to Refresh. Change its font to Custom, Avenir Next, Medium, Size 16.

watch-button

That looks good!

watch-storyboard4

Note that you can change things like text and titles and colors from code at runtime, but you can only access font and positioning properties here in the storyboard. So you could be spending a little more time in Interface Builder for your Watch apps compared to iOS apps if you’re used to setting up your layouts in code.

Note: Just another friendly reminder that WatchKit is in beta – so it’s possible Apple will open up more methods and properties on interface objects some time in the future.

Actions and Outlets

Actions and outlets work just as you would expect, allowing you to access interface elements from code and to handle user actions such as button presses.

If you think too hard, it seems strange to connect these things since the storyboard is in the Watch App target and the code is in the WatchKit Extension target.

How can the actions and outlets connect across targets? Or across devices for that matter, since the app is on the Watch and the extension is on the phone!

Luckily, this is the magic part that Apple handles behind the scenes so you don’t have to worry about it.

Remote actions and outlets? Don't give it another thought!

Remote actions and outlets? Bluetooth magic!

Behind the scenes, Apple takes care of all the wireless communication between your WatchKit app and your iPhone’s WatchKit extension behind the scenes, using Bluetooth. Pretty cool, eh?

Open the Assistant editor and make sure InterfaceController.swift is showing up there. Control-drag from the price label to inside the InterfaceController class definition to create an outlet, call it priceLabel, and click Connect.

watch-outlet

Next, control-drag from the button to the class. This time, make sure to select Action to create a method rather than an outlet. Call the action method refreshTapped, and click Connect.

That’s it for the interface so far. It’s time to switch to the code and get some data showing up!

Basic WatchKit Code

The starter project includes a framework called BitWatchKit that contains some custom code to fetch the Bitcoin price. We put this code in a framework so it can be easily used in both the iPhone app, and the iPhone WatchKit extension.

To add this framework to your extension, open the project file in the navigator and select the BitWatch WatchKit Extension target. Select the General tab and then scroll down to the Linked Frameworks and Libraries section.

watch-extension

Press the + button under the list of frameworks. You’ll see a window listing the available frameworks to add, and you should see BitWatchKit.framework right at the top of the list. Select it and click Add.

watch-framework

Now that the framework is there, you can update your Watch interface with some real data!

Switch over to InterfaceController.swift in the WatchKit Extension group. Add the following import statement to the top of the file:

import BitWatchKit

This will allow you to access the Tracker class defined in the framework.

Next, add the following properties to the class definition:

let tracker = Tracker()
var updating = false

You’ll need an instance of Tracker to access the Bitcoin values over the network. You’ll use updating to keep track of whether there’s a pending request to update the price.

Add the following helper method to the class:

private func updatePrice(price: NSNumber) {
  priceLabel.setText(Tracker.priceFormatter.stringFromNumber(price))
}

This method takes an NSNumber and updates the label on the Watch. Tracker also includes a handy number formatter that will take a number like 93.1 and turn that into a string like “$93.10″.

Add one more helper method to the class:

private func update() {
  // 1
  if !updating {
    updating = true
    // 2
    let originalPrice = tracker.cachedPrice()
    // 3
    tracker.requestPrice { (price, error) -> () in
      // 4
      if error == nil {
        self.updatePrice(price!)
        self.updating = false
      }
    }
  }
}

Let’s review this step by step:

  1. Runs a quick check to make sure you’re not updating already.
  2. Caches the current price so you can only update the UI if the price changes.
  3. requestPrice() is a method on the Tracker class that gets the latest Bitcoin price via a network request. Once it completes, it executes a closure.
  4. If the request was successful, all you need to do is call updatePrice() to update the label.

Now you just need to call these methods from somewhere to get the real data to appear.

Interface Lifecycle

The initializer init(context:) is like a combination of init + viewDidLoad. After the call to super, the entire interface is loaded and the actions and outlets are wired up.

That means you can set up the interface objects and start displaying data. However, the interface isn’t necessarily going to be on the Watch just yet! So it’s important to get things to a reasonable state, but you might not want to perform expensive operation such as making network calls.

Add the following to the end of init(context:):

updatePrice(tracker.cachedPrice())

This will update the price label with the previously cached value, if one exists. Since this value is cached by the tracker, it’s local data and inexpensive to fetch.

Add the following to the end of willActivate():

update()

willActivate is like viewWillAppear in iOS, and means the interface is about to activate and appear on the Watch. This is a good signal to refresh the data, so calling update() here will kick off the network request and update the label.

Finally, add the same line of code to refreshTapped:

update()

When the user taps Refresh, you want the same thing to happen: call update() and display some fresh data.

Testing Your App

To test your Watch app, you’ll need to enable the Watch as an external display. If you have the iOS Simulator app running, switch to it. Otherwise, build and run the app, stop it in Xcode, and then switch back to the iOS Simulator. In the menu, navigate to Hardware\External Displays and select one of the Apple Watch options.

watch-simulator

You should have two simulator windows now – one showing the iPhone, and one for the watch.

Back in Xcode, select the BitWatch Watch App scheme from the toolbar, and select the iPhone 6 Simulator.

watch-scheme

Build and run and then switch to the simulator. You should see the Bitcoin data on the Apple Watch!

watch-app1

More Interface

Now that you have the basics in place, it’s time to look at some more things you can do with the interface.

There are two more elements from the app you’ll implement on the Watch too:

  • The last update time
  • An up / down image to show whether the price has increased or decreased

To do this, you’ll first need to add the images to the asset catalog. Open Images.xcassets in the BitWatch Watch App group. Be careful here: there are three Images.xcassets files in the project so make sure you’re in the correct group!

The starter project includes two image files: Down@2x.png and Up@2x.png. From a finder window, drag those two files to the list of image sets.

watch-assets

Note that only the @2x variant of the image is included. On WatchKit you only need @2x images – there are no non-Retina Apple Watches!

Next, open Interface.storyboard again to return to the layout.

Drag another label to the interface and place it above the Refresh button. Set the text to Last Updated and the font to Custom, Avenir Next, Regular, and Size 13. Also change the text alignment to center.

For the position settings, change the Horizontal position to Center and the Vertical position to Bottom.

watch-storyboard5

You might be wondering: if both the Refresh button and this new label have their position set to Bottom, how does the Watch know what order to place them in?

On iOS, the view order in the document outline determines the z-order, or which views are “on top” or “behind” other views. For Watch interfaces, interface elements do not overlap so there’s no need to keep track of z-order. Instead, the order in the document outline reflects the vertical top-to-bottom order (in combination with the vertical alignment settings).

watch-outline

To see what I mean, swap the order of the Refresh and Last Updated controls in the outline – you’ll see them swap visually in interface builder as well. Swap them back when you’re done.

Groups

So far, your Watch interface consists of interface object stacked on top of each other. But what about side-by-side objects?

Another object available for your use are groups. Groups are like containers, and you can put interface objects inside them. However, they also have a setting for whether they should lay out their items horizontally or vertically. You can have groups inside groups with different layouts to achieve all kinds of arrangements!

Note: Groups can be interface elements too, with background colors and images. For this app, they’ll be invisible to the user – except for how they contribute to the layout, of course!

Here’s what the price increase/decrease indicator looks like in the iPhone app:

watch-apparrows

So the overall plan is to have an image next to a label.

Drag a new group from the object library to the storyboard, and place it between the price label and the last updated label. In the Attributes inspector, make sure the group’s Layout setting is set to Horizontal.

watch-storyboard6

Next, drag an image from the object library and place it inside the group. Then drag in a new label to the group and place it to the right of the image.

watch-storyboard7

You have your two objects side-by-side, but now it’s a matter of setting their position and size while they’re inside a group.

Position and Size

Select the label and change both its Horizontal and Vertical positions to Center. Do the same for the image.

Since the image and label are inside a group, they are centered within the group and not within the outside world of the storyboard as a whole. That means as the group moves around and if it were resized for some reason, the image and label would stay right in the center of the group.

By default, most interface objects have their size set to Size to Fit Content. This works well for labels, since the text may change. However, the arrow images are right in the bundle and have a known size so you can set the size in the storyboard. That means the Watch won’t have to calculate the size on the fly later.

Select the image and find the Size section in the Attributes inspector. Change the Width setting to Fixed Width and the Height setting to Fixed Height. Set both the width and height values to 32.

watch-size

Wrapping Up the Interface

The label’s text won’t change so you only need to set it up once in the storyboard. Change its text to 1 BTC and set the font to Custom, Avenir Next, Regular, and Size 13.

The image will change depending on the previous Bitcoin value so you’ll need an outlet. Open the assistant editor and control-drag from the image to the class definition. Call the outlet image, and click Connect.

You’ll need one more outlet for the “Last Updated” label. Control-drag from the label to the class definition and call the outlet lastUpdatedLabel, and click Connect.

That’s it for the interface – there’s a little more code and some tricks about updating the layout from code before you start tracking your Bitcoin-funded retirement!

Wrapping Up the Code

Open InterfaceController.swift. There are two new things to deal with: the “last updated” label and the image.

Add the following helper method to the class:

private func updateDate(date: NSDate) {
  self.lastUpdatedLabel.setText("Last updated \(Tracker.dateFormatter.stringFromDate(date))")
}

This method updates the last updated label similar to how the price label method does its job. Again, the Tracker has a formatter object that in this case, will format the date to just show the time.

Add one more helper method to the class:

private func updateImage(originalPrice: NSNumber, newPrice: NSNumber) {
  if originalPrice.isEqualToNumber(newPrice) {
    // 1
    image.setHidden(true)
  } else {
    // 2
    if newPrice.doubleValue > originalPrice.doubleValue {
      image.setImageNamed("Up")
    } else {
      image.setImageNamed("Down")
    }
    image.setHidden(false)
  }
}

This method will compare the original price to the new price.

  1. If the two prices are the same, hide the arrow image.
  2. If the prices are different, set the image to either “Up” or “Down” depending on the direction the price changed. Unhide the image to make it visible again.

Calling setHidden will cause the layout to reflow. Remember how both the image and the “1 BTC” label are centered inside the group? When you hide the image, that will leave extra space to the left of the label. However, the interface is smart enough to then recalculate the layout and move the label over so it remains centered inside the group.

Note: You can hide things and still have them “take up space” by setting an interface object’s alpha to 0 with setAlpha.

Now that the helper methods are in place, you need to call them. Add the following lines to the end of init(context:):

image.setHidden(true)
updateDate(tracker.cachedDate())

Since you don’t have any previous price information on launch, you start out with a hidden image. The price is coming from the cache, so you can grab the date from the cache too.

Next, find update() and add the following lines inside the innermost if block with the if error == nil { condition:

self.updateDate(NSDate())
self.updateImage(originalPrice, newPrice: price!)

This will update the last updated label to show the current time. By this point, you have both the previous price and the new price so you can use updateImage to show the up or down arrow image if the price has changed.

Build and run the Watch app and have a look at the simulator.

watch-final

You should see the current price displayed as before. Since you ran the app once already there should be a cached price and if it’s different, you should see an up or down arrow too.

The source data is updated every minute, so you can wait a minute and tap the Refresh button for the latest Bitcoin price. Can you afford that solid gold Apple Watch Edition yet? ;]

Where to Go From Here?

Here is the final example project from the above WatchKit tutorial

Congratulations – you now know the basics of making WatchKit apps!

But there’s a lot more to learn – including additional interface objects, navigation and handoff. And keep in mind that this is just one type of WatchKit app you can make – you can also make Glances (similar to Today extensions), or Custom Actionable Push Notifications.

To learn more, check out Apple’s WatchKit Programming Guide and Apple Watch Human Interface Guidelines. We’ll also be hard at work making more written tutorials, video tutorials, and maybe even a book – stay tuned!

In the meantime, let us know how your own WatchKit adventures are going in the comments below.

WatchKit Tutorial with Swift: Getting Started is a post from: Ray Wenderlich

The post WatchKit Tutorial with Swift: Getting Started appeared first on Ray Wenderlich.

Video Tutorial: iOS App Extensions Part 4: Photo Extensions: Shared Settings

Readers’ App Reviews – November 2014

$
0
0
FlyDive

When Pigs Fly…

November came up fast! Its another month of great apps, from the best developer community on the web.

Your fellow readers have been hard at work making fantastic apps and games to share with you.

This month we’ve got:

  • A wacky shooter
  • A coloring book for your kids
  • A app that speaks your tweets
  • And of course, much more!

Read on to see some great apps from your community!

NUM – Insanely Hard Math Game

NUM
NUM is like a calculator turned into a game.

NUM gives you a target number and 6 random numbers. Using basic addition, subtraction, multiplication, and division, you have to use some or all of those 6 numbers to make the target number.

There is a time limit, so work quickly, but be creative. Its not all just addition to get to the top. You can retry each level with new numbers. With an in app purchase you can see the solutions if you’re curious, but they don’t affect the game.

Countdowns with Widget

Countdowns
We’ve all got things we’re looking forward to like Christmas, WWDC, or Frozen 2. Wouldn’t it be nice if there was a sweet app to help?

There is an app for that! Its called Countdowns, and it couldn’t be simpler. Just open the app and tap Add New Countdown. A name and a date are all it takes. But if you want, you can also specify a time and select the units you’d like to countdown like days or hours.

And to top it off, Countdowns is iOS8 ready with a fancy Widget! You can choose to include each countdown in your Notification center separately so it won’t clog up your notification center just because you have a lot of countdowns.

FlyDive

FlyDive
FlyDive is a classic sidescrolling flying game with a fun twist: building your own flying machine!

FlyDive features over 10,000 combinations of parts for your very own flying creation. You can be a dog flying a hotdog with wheels. Or you can take to the skies as a robot in a bathtub with flipflops for wings. Or you can pilot favorite biplane with candy bars for wings as a pig.

The possibilities go on and on. Gameplay is simple: collect balloons, avoid bombs, and grab a few powerups along the way. See who can fly the furthest!

MadNotes

MadNotes
Note taking should be simple. If it’s not, most of us won’t take notes.

MadNotes helps in that department. MadNotes is a dead simple note taking app, where simple gesture based controls keep the interface clear of clutter. A quick pull down will create a new note. Swipe left to delete, right to prioritize.

MadNotes makes it easy to colorize as well. Just touch and hold to cycle through colors. And each note is easily sharable with friends.

Stars and Stones

Stones
Stars and Stones is a simple game, but that doesn’t make it easy.

The goal is to collect five stars on each level one at a time by dragging you finger to them. But of course there is a catch. Oh and its a good catch. As you drag your finger, stones slide around. The faster you move, the faster they move.

You must weave your way among the stones while they do their carefully choreographed routine to the beat of your drum. Powerups along the way help by providing a boost to life or getting rid of a few stones.

Stomper Revenge

Stomper
Stomper has had a rough day. A group of monsters and foxes took his crown and threw him down down a black hole. Now he’s fighting his way back to the top and he needs your help.

Stomper’s only hope is to stomp all the tiles out of his way. Foxes and Skunks won’t stand in the way of his massive stompers. Travel far and wide, stomping the land, collection trinkets along the way to help you on your quest.

What out for monsters, they’re not as easy to stomp as the foxes and skunks.

Number March

NumberMarch
Number March is a tower defense game where you’ll need a little math to defend your empire.

Number March uses division to bring down the onslaught of numbers marching toward your base. Each time a number passes by a block that is one of its factors, it will divide and subtract one from the block.

Line your walls with common factors, but be ready for the big numbers and primes. They’re not afraid of your puny 2’s and 3’s.

Cocoa Assist

CocoaAssist
Cocoa Assist might be the new must have for iOS developers.

Cocoa Assist queries the CocoaPods, CocoaControls, OSChina, and Code4App databases to find reusable components for your apps. Its easy to search each database or all.

If the control has an animated gif showing what it does, it will display it. And you can bookmark the components you like for quick access later. This will put all the best components for your apps at your fingertips.

Coloring book: Cat’s adventure

CatColoring
If you’ve got kids or love just coloring Cats, this is a great app for your iPad.

There are more than 20 adorable cats dressed up just for you. Each picture comes with only the colors you need to complete the page. The app is aimed at the younger crowd, so it also keeps your colors in the lines for you automagically.

Kids will love their creations when they’re finished so be prepared for show and tell. :]

TweetSpeak

SpeakTweet
There are plenty of things to listen to on your iPhone. But for us Twitterphiles something has been missing, until now.

TweetSpeak will read your Twitter feed aloud to you one tweet at a time. Its completely hands free, just start it and you can background the app and carry on. Its got settings to adjust read speed and supports four different languages.

TweetSpeak is great for catching up with Twitter while you’re driving home or cooking dinner.

White House Fence Jumper

WhiteHouse
When someone jumps the fence at the White House, its a national affair. And how better to handle it than a giant cannon?

Its not just protesters going over the fence though. The aliens are here and we must stop them! Its up to you to man the last cannon and take down anyone trying to make it across the lawn.

The country is counting on you. Your parents are counting on you. And that guy on the skateboard doesn’t look human. What are you waiting for!?



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.

Your Share
Do You Know Your Countries?
Make your team guess!
Numbed
Grappling Sheep
Blue Guy
meme generator best free



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 saw an app your liked, hop to the App Store and leave a review! A good review always makes a dev’s day. And make sure you tell them you’re from Ray Wenderlich, this is a community of makers!

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 – November 2014 is a post from: Ray Wenderlich

The post Readers’ App Reviews – November 2014 appeared first on Ray Wenderlich.

Video Tutorial: WatchKit Part 0: Introduction

Video Tutorial: WatchKit Part 1: Interface – Positioning

WatchKit with Ben Morrow – Podcast S02 E07

$
0
0
Learn about WatchKit with Ben Morrow!

Learn about WatchKit with Ben Morrow!

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

We hope you’ve been enjoying the new weekly format of the podcast so far. There’s only a few episodes left in season 2!

In this episode, we talk with Tutorial Team member Ben Morrow, organizer of the recent WatchKit Hackathon, to discuss our initial impressions of the new WatchKit SDK.

[Subscribe in iTunes] [RSS Feed]

Links and References

Contact Us

Where To Go From Here?

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

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

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

WatchKit with Ben Morrow – Podcast S02 E07 is a post from: Ray Wenderlich

The post WatchKit with Ben Morrow – Podcast S02 E07 appeared first on Ray Wenderlich.

Video Tutorial: WatchKit Part 2: Interface – Size


RWDevCon Schedule Now Available!

$
0
0

As you probably know, the team and I are running an iOS conference next February focused on hands-on, high quality tutorials: RWDevCon.

Recently we put out a poll to all attendees, and had everyone vote on their favorite topics – we chose the top-voted topics, and built a schedule around that.

Today, we are happy to announce the schedule! You can check it out here:

The team and I are really excited about the schedule – we think it has something for everyone, from complete beginners to iOS gurus.

Now we’re hard at work preparing our tutorial materials. We can’t wait to reveal it all in DC this February! :]

RWDevCon Schedule Now Available! is a post from: Ray Wenderlich

The post RWDevCon Schedule Now Available! appeared first on Ray Wenderlich.

Beginning Alamofire Tutorial

$
0
0
Learn how to easily make network requests in Swift with Alamofire!

Learn how to easily make network requests in Swift with Alamofire!

AFNetworking is one of the most popular third-party libraries on iOS and OS X. It was awarded the 2012 Best iOS Library Award in our 2012 Reader’s Choice Awards, and is one of the most widely-used open source projects on Github with over 14K stars and 4K forks.

Recently the creator and maintainer of AFNetworking, Mattt Thompson, released a new networking library like AFNetworking, but designed specifically to use modern Swift conventions and language features: Alamofire.

The AF prefix in AFNetworking is short for Alamofire, so even the new library name is based on the new Swift conventions! :]

In Part 1 of this two part series, you will use Alamofire to build a photo gallery app that uses 500px.com as its feed. Along the way you’ll learn about Alamofire’s most important components and some other important aspects of handling network requests in your apps.

Part 2 will continue on from Part 1 and add some more functionality and polish to the app, learning about more advanced Alamofire features along the way.

This tutorial assumes you are familiar with Swift and iOS development – if you are not, please check out some of our other tutorials. Also, please use Xcode 6.1 for this tutorial (not Xcode 6.2 beta).

Note: If you’re familiar with the basics of Alamofire, you can jump straight to the second part of this tutorial. But make sure you obtain your consumer key first and replace it in the app as described below.

Getting Started

Download the starter project for this tutorial; it provides all UI code required to finish the project in this tutorial. This lets you focus on learning about Alamofire rather than mucking about with UI design.

Open up the project in Xcode and have a look at Main.storyboard:

1

The app uses a common UI pattern with a UITabBarController as the root screen. The tab controller contains two tabs, each with its own UINavigationController. The first tab lets users browse popular photos. The second tab allows users to browse photos they’ve already saved. Both tabs display photos to the user in a UICollectionViewController. The storyboard also contains two standalone view controllers which will be used later in the tutorial.

Build and run your app; you’ll be greeted with a spinner that spins forever:

Spinner on Alamofire Starter Project

That’s not terribly cool — there’s not much to look at. But you’ll soon change that with some Alamofire code.

Note: If you’re familiar with AFNetworking, you might expect that the next section will discuss CocoaPods. Unfortunately, at the time of writing this tutorial there’s no straightforward way to integrate a Swift library via CocoaPods.

While a few people have found and posted solutions around the Internet, the steps below use the time-proven method of manually copying the code into your project.

To get the latest version of Alamofire, head to https://github.com/Alamofire/Alamofire and click the Download ZIP button found on the right hand side of the page. Open the starter project’s folder in Finder and drag the Alamofire-master folder into your main project folder.

DragFolder

Open the Alamofire-master folder (now in a subdirectory of your project folder) and drag Alamofire.xcodeproj — the file with a blue icon, not the white icon — straight into Xcode right below Photomania, like so:

Next, click on Photomania and ensure you’ve selected the General tab. Scroll down and click the + below Embedded Binaries, then choose Alamofire.framework and finally click on Add:

0.1

Build and run your project to make sure you have no errors, then move onto the next section.

Retreiving Data with Alamofire

You might be asking why you need Alamofire in the first place. Apple provides the NSURLSession class and related classes for downloading content via HTTP, so why complicate things with another third party library?

The short answer is that Alamofire is based on NSURLSession, but it frees you from writing boilerplate code and makes writing networking code much easier. You can access data on the Internet with very little effort, and your code will be much cleaner and easier to read.

To use Alamofire, you first need to import it. To do this, open PhotoBrowserCollectionViewController.swift and add the following line to the top of the file:

import Alamofire

You’ll need to add this import statement to every file that accesses Alamofire classes and functions.

Next, add the following code to viewDidLoad() just below setupView():

Alamofire.request(.GET, "https://api.500px.com/v1/photos").responseJSON() {
  (_, _, data, _) in
  println(data)
}

I’ll explain this in a moment, but first build and run your app; you’ll see the following message appear in the console:

Optional({
    error = "Consumer key missing.";
    status = 401;
})

You might not realize it, but you just made your first network request with Alamofire. You requested a resource on the Internet and received a JSON response in return.

Here’s what’s going on under the hood:

  • Alamofire.request(_:_) accepts two required parameters: method, which is usually .GET or .POST; and URLString, which is the URL of the content you wish to access. You then receive an Alamofire.Request object in response.
  • Typically, you’ll simply chain the request object to a response method; for example, in the code above the request object simply calls responseJSON(). responseJSON will call the supplied closure when the request completes. In this code, you simply output the parsed JSON to the console.
  • By calling responseJSON, you indicate that you expect a JSON response. In this case, Alamofire attempts to parse the response and return a JSON object. Alternatively, you could request a property list using responsePropertyList or even a raw string using responseString. You’ll learn more about these response serializers later in the tutorial.

As you can see from the response logged to the console, the server says that you need something called a consumer key. Before you can go any further, you’ll need a key for the 500px API.

Obtaining Your Consumer Key

Go to https://500px.com/signup and register for free using your email address, or by way of your Facebook, Twitter or Google login.

Once you’ve completed your registration, go to https://500px.com/settings/applications and click on “Register your application”.

You’ll see the following dialog box:

Those fields with big red arrows are all required. Add the Application Name as Alamofire Tutorial and the Description as iOS App. Your app doesn’t have an Application URL, but enter any valid URL to satisfy the Register Application requirements — perhaps raywenderlich.com will do! :]

Finally, enter your email address in the Developer’s Email field and click the checkbox to accept the Terms of Use.

Finally, click on the Register button; you’ll see a box like the following:

Click on the See application details link and it will expand to show you your consumer key as follows:

Copy the consumer key from this screen, then head back to Xcode and replace the first and only piece of code you’ve added so far with the following:

Alamofire.request(.GET, "https://api.500px.com/v1/photos", parameters: ["consumer_key": "PASTE_YOUR_CONSUMER_KEY_HERE"]).responseJSON() {
  (_, _, JSON, _) in
  println(JSON)
}

Make sure you replace PASTE_YOUR_CONSUMER_KEY_HERE with the consumer key you copied above.

Build and run your app; this time, you’ll see a LOT more output in the console:

All the output above means you’ve successfully downloaded some JSON that contains information about some photos.

The JSON response contains a few properties about the set of images, some paging information, and an array of photos. Here’s the search result I got (yours may look slightly different):

{
  "feature": "popular",
  "filters": {
      "category": false,
      "exclude": false
  },
  "current_page": 1,
  "total_pages": 250,
  "total_items": 5000,
  "photos": [
    {
      "id": 4910421,
      "name": "Orange or lemon",
      "description": "",
	.
	.
	.
      }
    },
    {
      "id": 4905955,
      "name": "R E S I G N E D",
      "description": "From the past of Tagus River, we have History and memories, some of them abandoned and disclaimed in their margins ...",
	.
	.
	.
    }
  ]
}

Now that you have the JSON response you can do something useful with it.

Replace println(JSON) in viewDidLoad() with the following:

let photoInfos = (JSON!.valueForKey("photos") as [NSDictionary]).filter({
    ($0["nsfw"] as Bool) == false
  }).map {
    PhotoInfo(id: $0["id"] as Int, url: $0["image_url"] as String)
  }
 
self.photos.addObjectsFromArray(photoInfos)
 
self.collectionView.reloadData()

The above code translates the JSON response into an array of more manageable PhotoInfo objects. These objects are just simple buckets for the photo’s ID and URL properties. You’ll also notice the code filters out pictures that…well…you might not want popping up unexpectedly! ;]

The above code also reloads the collection view. The sample code from the starter project creates cells in the collection view based on the photos array you populated.

Build and run your app; this time the spinner disappears after a while and if you look closely, you’ll see a bunch of dark grey square cells:

You’re getting closer!

Still in PhotoBrowserCollectionViewController.swift, add the following code to collectionView(_: cellForItemAtIndexPath:) just before return cell:

let imageURL = (photos.objectAtIndex(indexPath.row) as PhotoInfo).url
 
Alamofire.request(.GET, imageURL).response() {
  (_, _, data, _) in
 
  let image = UIImage(data: data! as NSData)
  cell.imageView.image = image
}

The above code creates another Alamofire request for an image from the photos array. Because this is an image request, you’re using the simple request method which returns the response in an NSData blob. You then put the data directly into an instance of UIImage and, in turn, put that into an image view that already exists in the sample project.

Build and run your app once again; a collection of images should appear, similar to the screenshot below:

You have your proof-of-concept Alamofire requests working just fine, but you don’t want to copy and paste the API address and add your consumer key every single time you request something from the server. Aside from it being ugly and cumbersome, imagine the code rework you’d face if the API address changed or you had to use a new consumer key!

Fortunately, Alamofire has a solution to that exact problem.

Creating a Request Router

Open Five100px.swift and find struct Five100px, which defines enum ImageSize. This is a very simple data structure based on the 500px.com API documentation.

Since you’re going to add some Alamofire code, add the following requisite import to the top of the file:

import Alamofire

Now, add the following code inside struct Five100px, just above enum ImageSize:

enum Router: URLRequestConvertible {
  static let baseURLString = "https://api.500px.com/v1"
  static let consumerKey = "PASTE_YOUR_CONSUMER_KEY_HERE"
 
    case PopularPhotos(Int)
    case PhotoInfo(Int, ImageSize)
    case Comments(Int, Int)
 
    var URLRequest: NSURLRequest {
      let (path: String, parameters: [String: AnyObject]) = {
        switch self {
        case .PopularPhotos (let page):
          let params = ["consumer_key": Router.consumerKey, "page": "\(page)", "feature": "popular", "rpp": "50",  "include_store": "store_download", "include_states": "votes"]
          return ("/photos", params)
        case .PhotoInfo(let photoID, let imageSize):
          var params = ["consumer_key": Router.consumerKey, "image_size": "\(imageSize.rawValue)"]
          return ("/photos/\(photoID)", params)
        case .Comments(let photoID, let commentsPage):
          var params = ["consumer_key": Router.consumerKey, "comments": "1", "comments_page": "\(commentsPage)"]
          return ("/photos/\(photoID)/comments", params)
        }
        }()
 
        let URL = NSURL(string: Router.baseURLString)
        let URLRequest = NSURLRequest(URL: URL!.URLByAppendingPathComponent(path))
        let encoding = Alamofire.ParameterEncoding.URL
 
        return encoding.encode(URLRequest, parameters: parameters).0
  }
}

This is your router, which creates appropriate instances of URLString for your API calls. It’s a simple enum that conforms to URLRequestConvertible, which a protocol defined inside Alamofire. When an enum adopts this protocol, it must have a variable of type NSURLRequest named URLRequest.

Your router has two static constants: the baseURLString of your API and your consumerKey. For the last time (promise!), replace PASTE_YOUR_CONSUMER_KEY_HERE with your own individual consumer key. From now on, the router will add your consumer key to the final URLString as necessary.

Your app has three API endpoints: one to retrieve a list of popular photos, one to retrieve more information about a specific photo and one to retrieve for comments on a photo. Your router handles these three conditions with three corresponding case statements, each of which accepts a parameter or two.

You’ve defined var URLRequest: NSURLRequest as a computed property; this means that each time you use this enum, it constructs the resulting URL on-the-fly based on the specific case and its parameters.

Here’s an example snippet of code that illustrates the above logic:

Five100px.Router.PhotoInfo(10000, Five100px.ImageSize.Large)
// URL: https://api.500px.com/v1/photos/10000?consumer_key=xxxxxx&image_size=4
// https://api.500px.com/v1  +  /photos/10000  +  ?consumer_key=xxxxxx&image_size=4
// = baseURLString  +  path  +  encoded parameters

In the above example, the code routes through the photo info API endpoint looking for a large-sized photo with an ID of 10000. The commented lines break down the construction of the URL. In this case, the URL has three components: the baseURLString, the path (the part that comes before the “?”), and a [String: AnyObject] dictionary of the parameters to pass to your API endpoint.

For path, the first element of the return tuple, you’re using a simple Swift string interpolation as follows:

"/photos/\(photoID)" // "/photos/10000"

Similar to response parsing, request parameters can be encoded as JSON, property lists, or strings. Typically, you’ll use simple string parameters as in the code above.

If you’re planning to use this router in your own projects, you’ll need to be extremely familiar with how it works. To that end, try figuring out how you’d construct the following URL:

https://api.foursquare.com/v2/users/{USER_ID}/lists?v=20131016&group=created

Solution Inside: Solution SelectShow>

How did you do? If you weren’t 100% sure of the answer, take a bit of time and analyze the router code until you’re comfortable with how it works.

Loading More Photos

Your app currently shows only one page of photos. You’ll fix that so you can browse photos to your heart’s content. More is always better, right? :]

Open PhotoBrowserCollectionViewController.swift and add the following code below let refreshControl = UIRefreshControl():

var populatingPhotos = false
var currentPage = 1

There are two variables to keep track of whether you’re currently populating photos, and what the current page of photos is.

Next, replace the current implementation of viewDidLoad() with the following:

override func viewDidLoad() {
  super.viewDidLoad()
 
  setupView()
 
  populatePhotos()
}

Here you replace the Alamofire request you had previously with a call to populatePhotos(), which you’ll write in a moment.

Still working in the same file, add the following two functions above handleRefresh():

// 1
override func scrollViewDidScroll(scrollView: UIScrollView) {
  if scrollView.contentOffset.y + view.frame.size.height > scrollView.contentSize.height * 0.8 {
    populatePhotos()
  }
}
 
func populatePhotos() {
  // 2
  if populatingPhotos {
    return
  }
 
  populatingPhotos = true
 
  // 3
  Alamofire.request(Five100px.Router.PopularPhotos(self.currentPage)).responseJSON() {
    (_, _, JSON, error) in
 
    if error == nil {
      // 4
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
        // 5, 6, 7
        let photoInfos = ((JSON as NSDictionary).valueForKey("photos") as [NSDictionary]).filter({ ($0["nsfw"] as Bool) == false }).map { PhotoInfo(id: $0["id"] as Int, url: $0["image_url"] as String) }
 
        // 8
        let lastItem = self.photos.count
        // 9
        self.photos.addObjectsFromArray(photoInfos)
 
        // 10
        let indexPaths = (lastItem..<self.photos.count).map { NSIndexPath(forItem: $0, inSection: 0) }
 
        // 11
        dispatch_async(dispatch_get_main_queue()) {
          self.collectionView.insertItemsAtIndexPaths(indexPaths)
        }
 
        self.currentPage++
      }
    }
    self.populatingPhotos = false
  }
}

There’s a lot going on here. Taking each commented section in turn:

  1. scrollViewDidScroll() loads more photos once you’ve scrolled through 80% of the view.
  2. populatePhotos() loads photos in the currentPage and uses populatingPhotos as a flag to avoid loading the next page while you’re still loading the current page.
  3. You’re using your fancy router for the first time here. You simply pass in the page number and it constructs the URL string for that page. 500px.com returns at most 50 photos in each API call, so you’ll need to make another call for the next batch of photos.
  4. Make careful note that the completion handler — the trailing closure of .responseJSON() — must run on the main thread. If you’re performing any long-running operations, such as making an API call, you must use GCD to dispatch your code on another queue. In this case, you’re using DISPATCH_QUEUE_PRIORITY_HIGH to run this activity.
  5. You’re interested in the photos key of the JSON response that includes an array of dictionaries. Each dictionary in the array contains information about one photo.
  6. Here you use Swift’s filter function to filter out NSFW (Not Safe For Work) images.
  7. The map function takes a closure and returns an array of PhotoInfo objects. This class is defined in Five100px.swift. If you look at the code of this class, you’ll see that it overrides both isEqual and hash. Both of these methods use an integer for the id property so ordering and uniquing PhotoInfo objects will still be a relatively fast operation.
  8. Next you store the current number of photos before you add the new batch; you’ll use this to update collectionView.
  9. If someone uploaded new photos to 500px.com before you scrolled, the next batch of photos you get might contain a few photos that you’d already downloaded. That’s why you defined var photos = NSMutableOrderedSet() as a set; since all items in a set must be unique, this guarantees you won’t show a photo more than once.
  10. Here you create an array of NSIndexPath objects to insert into collectionView.
  11. Inserts the items in the collection view – but does so on the main queue, because all UIKit operations must be done on the main queue.

Build and run your app and scroll slowly through the photos; you’ll see that new photos are continually loaded as you page through the app:

Slow Scroll

Scroll through the photos a bit faster now…a little faster again…notice any issues? Yep, the scrolling is pretty choppy. That’s not the kind of experience you want to offer your users — but fortunately you’ll fix this issue in the next section.

Creating Custom Response Serializers

You’ve already seen how simple it is to use the provided JSON, string, and property list serializers in Alamofire. But sometimes you’ll want to create your own custom response serialization. For example, instead of receiving an NSData that you then have to convert to a UIImage, you can write a custom response serializer to convert it directly to a UIImage for you.

In this section, you’ll learn how to do exactly that.

Open Five100px.swift and add the following code near the top of the file, just below the import Alamofire statment:

extension Alamofire.Request {
  class func imageResponseSerializer() -> Serializer {
    return { request, response, data in
      if data == nil {
        return (nil, nil)
      }
 
      let image = UIImage(data: data!, scale: UIScreen.mainScreen().scale)
 
      return (image, nil)
    }
  }
 
  func responseImage(completionHandler: (NSURLRequest, NSHTTPURLResponse?, UIImage?, NSError?) -> Void) -> Self {
    return response(serializer: Request.imageResponseSerializer(), completionHandler: { (request, response, image, error) in
      completionHandler(request, response, image as? UIImage, error)
    })
  }
}

In order to make a new response serializer, the first thing you need is a class function that returns a Serializer closure (imageResponseSerializer() in the example above). This closure is typealiased in Alamofire, accepts three parameters and returns two as shown in the snippet below:

public typealias Serializer = (NSURLRequest, NSHTTPURLResponse?, NSData?) -> (AnyObject?, NSError?)

Your class function (i.e. imageResponseSerializer()) accepts as arguments the underlying NSURLSession request and response objects along with a raw NSData representation of the data received from the server. The function then uses these objects to serialize the input into a meaningful data structure and return it from the function, along with any error that occurs during this process. In your case, you use UIImage to translate the data into an image object.

Usually when you create a response serializer, you’ll also want to create a new response handler to go with it and make it easy to use. You do this here with .responseImage(), which has a fairly simple job: it takes a completionHandler, a block of code in form of a closure that will execute once you’ve serialized the data from the server. All you need to do in your response handlers is call Alamofire’s own general purpose .response() response handler.

Let’s put this to use. Open PhotoBrowserCollectionViewController.swift and add the following property to PhotoBrowserCollectionViewCell underneath the imageView property:

var request: Alamofire.Request?

This will store the Alamofire request to load the image for this cell.

Now replace the contents of collectionView(_: cellForItemAtIndexPath:) with the following:

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserCellIdentifier, forIndexPath: indexPath) as PhotoBrowserCollectionViewCell
 
let imageURL = (photos.objectAtIndex(indexPath.row) as PhotoInfo).url
 
cell.imageView.image = nil
 
cell.request = Alamofire.request(.GET, imageURL).responseImage() {
  (request, _, image, error) in
  if error == nil && image != nil {
    if request.URLString == cell.request?.request.URLString {
      cell.imageView.image = image
    }
  }
}
 
return cell

Build and run your app; scroll through the images again and the experience should be much more smooth:

SmoothScroll

Why Is It Faster?

But what exactly did you change to make scrolling so much faster? The critical code is in collectionView(_: cellForItemAtIndexPath:). But before you can understand how the fix works, you’ll need to understand the asynchronous nature of network calls.

Network requests — and by extension, Alamofire requests — are asynchronous in nature. That means making a network request shouldn’t block the execution of the rest of your code. Network requests can potentially take a long time to return — and you don’t your entire UI to freeze while waiting for pictures to download!

That being said, asynchronous requests pose a challenge. What if the UI changed between the time the request was made and the time you received a response back from the server?

For example, UICollectionView has an internal mechanism that dequeues cells. Creating new cells is expensive, so rather than constantly creating new cells, collection views reuse existing cells that are no longer on the screen.

This means that the same cell objects, with the same memory addresses, will be used over and over again. Therefore it’s possible that between the time you made your Alamofire request and the time you received the image response, the user scrolled the cell off the screen and that image is no longer required. Maybe the cell was dequeued and is supposed to display a completely different image!

You did two things in the above code to handle this situation. First, when you dequeue a cell you invalidate the image by setting it to nil; this ensures you’re not displaying the previous image. Second, your request completion handler checks to make sure the cell’s URL is the same as the request’s URL. If they aren’t, clearly the cell has moved on to a different image, and your completion handler won’t waste cycles setting the wrong image in the cell.

Where to Go From Here?

You can download the finished project from this Part 1 of the tutorial here..

Note: If you are discarding your own work and using the finished project above, don’t forget to replace your consumer key as appropriate in Five100px.swift as instructed earlier in the tutorial.

You covered a lot in this tutorial — this is a great time to take a little break! At this point your app has basic photo browsing functionality thanks to Alamofire.

In the process, you’ve learned how to make a GET request with Alamofire, send parameters, create a request router, and even create your own response serializer.

In the second part of this tutorial, you’ll add the following functionality:

  • A photo viewer
  • The ability to view comments and other details
  • An option to download photos with a sleek inline progress bar
  • And yes, Virginia, there is a pull-to-refresh clause! :)

I hope you enjoyed this part of the tutorial and that you’ll join us for Part 2. If you had any questions or comments on Alamofire, come join us in the discussion below!

Beginning Alamofire Tutorial is a post from: Ray Wenderlich

The post Beginning Alamofire Tutorial appeared first on Ray Wenderlich.

Intermediate Alamofire Tutorial

$
0
0
Learn how to easily make network requests in Swift with Alamofire!

Learn how to easily make network requests in Swift with Alamofire!

Welcome back to the second and final part of our two-part series on Alamofire!

In the first part of the series, you covered some basic use cases of Alamofire such as making GET requests, sending parameters, creating a request router, and even creating a custom response serializer.

In the process, you built a cool a photo gallery app named Photomania.

In this second and final part of the series, you’ll add the following bits of functionality to the app:

  • A photo viewer
  • The ability to view comments and other details
  • An option to download photos with a sleek inline progress bar
  • Optimized network calls and image caching
  • And yes, Virginia, there is a pull-to-refresh clause! :)

Getting Started

You can keep working with your own project from last time, but if you want to start with a clean starter project (or if you bypassed the first section of this tutorial altogether!), you can download the finished project from Part 1 here.

Note: If you didn’t work through Part 1 of this tutorial, don’t forget that you’ll first need to obtain a consumer key from 500px.com and replace it as necessary in Five100px.swift. Instructions on how to get this key — and where to put it — are provided in Part 1: Beginning Alamofire Tutorial.

Build and run the starter project to refresh yourself on how everything works; the photo preview browser is functional, but tap on a photo and the photo doesn’t open up fullscreen as you’d expect. Looks like that’s your first problem to solve! :]

Creating the Photo Viewer

Let’s be honest — generics are arguably one of the most powerful features of any modern language, including Swift. It just wouldn’t be right if you didn’t use any in this project.

Open Five100px.swift and add the following code near the top of the file, just below import Alamofire:

@objc public protocol ResponseObjectSerializable {
  init(response: NSHTTPURLResponse, representation: AnyObject)
}
 
extension Alamofire.Request {
  public func responseObject<T: ResponseObjectSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self {
    let serializer: Serializer = { (request, response, data) in
      let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
      let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
      if response != nil && JSON != nil {
        return (T(response: response!, representation: JSON!), nil)
      } else {
        return (nil, serializationError)
      }
    }
 
    return response(serializer: serializer, completionHandler: { (request, response, object, error) in
      completionHandler(request, response, object as? T, error)
    })
  }
}

In the code above you’re extending Alamofire once again by adding a new response serializer. This time, you’ve added a .responseObject() function; as a generic function, it can serialize any data object that conforms to the ResponseObjectSerializable you defined above.

That means if you define a new class that has an initializer of the form init(response:representation:), Alamofire can automatically return objects of that type from the server. You’ve encapsulated the serialization logic right inside the custom class itself. Ahh — the beautiful elegance of object-oriented design!

The photo viewer uses the PhotoInfo class, which already conforms to ResponseObjectSerializable (it implements the required method). But you need to make this official by marking the class as conforming to ResponseObjectSerializable.

Open Five100px.swift and modify the class declaration of PhotoInfo to explicitly conform to ResponseObjectSerializable as follows:

class PhotoInfo: NSObject, ResponseObjectSerializable {
Note: Although not required reading, readers who are interested in learning how the representation parameter is serialized into a PhotoInfo object can browse through required init(response:representation:) to see how it’s done.

Open PhotoViewerViewController.swift — NOT PhotoBrowserCollectionViewController.swift! — and add the requisite import statement to the top of the file:

import Alamofire

Next, add the following code to the end of viewDidLoad():

loadPhoto()

You’ll get an error that loadPhoto() is missing, but don’t worry – you’ll write this method next!

Still working in the same file, add the following code just before setupView():

func loadPhoto() {
  Alamofire.request(Five100px.Router.PhotoInfo(self.photoID, .Large)).validate().responseObject() {
    (_, _, photoInfo: PhotoInfo?, error) in
 
    if error == nil {
      self.photoInfo = photoInfo
 
      dispatch_async(dispatch_get_main_queue()) {
        self.addButtomBar()
        self.title = photoInfo!.name
      }
 
      Alamofire.request(.GET, photoInfo!.url).validate().responseImage() {
        (_, _, image, error) in
 
        if error == nil && image != nil {
          self.imageView.image = image
          self.imageView.frame = self.centerFrameFromImage(image)
 
          self.spinner.stopAnimating()
 
          self.centerScrollViewContents()
        }
      }
    }
  }
}

This time you’re making an Alamofire request inside another Alamofire request’s completion handler. The first request receives a JSON response and uses your new generic response serializer to create an instance of PhotoInfo out of that response.

(_, _, photoInfo: PhotoInfo?, error) in indicates the completion handler parameters: the first two underscores (“_” characters) mean the first two parameters are throwaways and there’s no need to explicitly name them request and response.

The third parameter is explicitly declared as an instance of PhotoInfo, so the generic serializer automatically initializes and returns an object of this type, which contains the URL of the photo. The second Alamofire request uses the image serializer you created earlier to convert the NSData to a UIImage that you then display in an image view.

Note: You’re not using the router here because you already have the absolute URL of the image; you aren’t constructing the URL yourself.

The .validate() function call before requesting a response object is another easy-to-use Alamofire feature. Chaining it between your request and response validates that the response has a status code in the default acceptable range of 200 to 299. If validation fails, the response handler will have an associated error that you can deal with in your completion handler.

Even if there’s an error, your completion handler will still be called. The fourth parameter error is an instance of NSError, which contains a value that lets you respond to errors in your own custom fashion.

Build and run your project; tap on one of the photos and you should see it fill the screen, like so:

Huzzah! Your photo viewer is working; double-tap the image to zoom then scroll.

When your type-safe, generic response serializer initializes a PhotoInfo you don’t set just the id and url properties; there are a few other properties you haven’t seen yet.

Tap the Menu button in the lower left corner of the app and you’ll see some extended details for the photo:

7

Tap anywhere on the screen to dismiss the photo details.

If you’re familiar with 500px.com, you know that users tend to leave lots of comments on the best and brightest photos on the site. Now that you have the photo viewer working, you can move on to the comments viewer of your app.

Creating a Collection Serializer for Comments

For photos that have comments, the photo viewer will display a button indicating the number of comments. Tapping on the comments button will open a popover listing the comments.

Open Five100px.swift and add the following code below the import Alamofire statement:

@objc public protocol ResponseCollectionSerializable {
  class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}
 
extension Alamofire.Request {
  public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self {
    let serializer: Serializer = { (request, response, data) in
      let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
      let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
      if response != nil && JSON != nil {
        return (T.collection(response: response!, representation: JSON!), nil)
      } else {
        return (nil, serializationError)
      }
    }
 
    return response(serializer: serializer, completionHandler: { (request, response, object, error) in
      completionHandler(request, response, object as? [T], error)
    })
  }
}

This should look familiar; it is very similar to the generic response serializer you created earlier.

The only difference is that this protocol defines a class function that returns a collection (rather than a single element) — in this case, [Self]. The completion handler has a collection as its third parameter — [T] — and calls collection on the type instead of an initializer.

Still working in the same file, replace the entire Comment class with the following:

final class Comment: ResponseCollectionSerializable {
  class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Comment] {
    var comments = [Comment]()
 
    for comment in representation.valueForKeyPath("comments") as [NSDictionary] {
      comments.append(Comment(JSON: comment))
    }
 
    return comments
  }
 
  let userFullname: String
  let userPictureURL: String
  let commentBody: String
 
  init(JSON: AnyObject) {
    userFullname = JSON.valueForKeyPath("user.fullname") as String
    userPictureURL = JSON.valueForKeyPath("user.userpic_url") as String
    commentBody = JSON.valueForKeyPath("body") as String
  }
}

This makes Comment conform to ResponseCollectionSerializable so it works with your above response serializer.

Now all you need to do is use it. Open PhotoCommentsViewController.swift, and add the requisite import statement at the top of the file:

import Alamofire

Now add the following code to the end of viewDidLoad():

Alamofire.request(Five100px.Router.Comments(photoID, 1)).validate().responseCollection() {
  (_, _, comments: [Comment]?, error) in
 
  if error == nil {
    self.comments = comments
 
    self.tableView.reloadData()
  }
}

This uses your new response serializer to deserialize the NSData response into a collection of Comments, saves them in a property, and reloads the table view.

Next, add the following code to tableView(_:cellForRowAtIndexPath), just above the return cell statement:

cell.userFullnameLabel.text = comments![indexPath.row].userFullname
cell.commentLabel.text = comments![indexPath.row].commentBody
 
cell.userImageView.image = nil
 
let imageURL = comments![indexPath.row].userPictureURL
 
Alamofire.request(.GET, imageURL).validate().responseImage() {
  (request, _, image, error) in
 
  if error == nil {
    if request.URLString.isEqual(imageURL) {
      cell.userImageView.image = image
    }
  }
}

This displays the information from the comment in the table view cell, and also fires off a secondary Alamofire request to load the image (this is similar to what you did in part 1 of the series).

Build and run your app; browse through the photos until you find one with comments; you’ll know the photo has comments when a number displays beside the comments icon. Tap the Comments button and you’ll see the comments on this photo appear like so:

By now you’ve probably found a photo or two (or a hundred! :]) that you’d like to download to your device. The next section shows you how to do just that!

Displaying Download Progress

Your photo viewer has an action button in the middle of the bottom bar. It shows a UIActionSheet that should let you download a photo — but right now it does nothing. Up until now, you’ve only loaded photos from 500px.com into memory. How do you download and save a file to your device?

Open PhotoViewerViewController.swift and replace the empty downloadPhoto() with the following:

func downloadPhoto() {
  // 1
  Alamofire.request(Five100px.Router.PhotoInfo(photoInfo!.id, .XLarge)).validate().responseJSON() {
    (_, _, JSON, error) in
 
    if error == nil {
      let jsonDictionary = (JSON as NSDictionary)
      let imageURL = jsonDictionary.valueForKeyPath("photo.image_url") as String
 
      // 2
      let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
 
      // 3
      Alamofire.download(.GET, imageURL, destination)
 
    }
  }
}

Head through the code comment by comment and you’ll find the following:

  1. You first request a new PhotoInfo, only this time asking for an XLarge size image.
  2. Get the default location on disk to which to save your files — this will be a subdirectory in the Documents directory of your app. The name of the file on disk will be the same as the name that the server suggests. destination is a closure in disguise — more on that in just a moment.
  3. Alamofire.download(_:_:_) is a bit different than Alamofire.request(_:_) in that it doesn’t need a response handler or a serializer in order to perform an operation on the data, as it already knows what to do with it — save it to disk! The destination closure returns the location of the saved image.

Build and run your app; find your favorite photo, tap the action button and tap Save. You won’t see any visual feedback just yet, but go back to the tab view and tap the Downloads tab where you’ll see the photo you just saved.

You may ask, “Why not simply pass a constant location for the file?” That’s because you might not know the name of the file before you download it. In the case of 500px.com, the server will always suggest 1.jpg, 2.jpg, 3.jpg, 4.jpg or 5.jpg based on the size of the file, and you can’t save files with the same filename overtop of each other.

Instead of passing a constant location as a string, you pass a closure as the third parameter of Alamofire.download. Alamofire then calls this closure at an appropriate time, passing in temporaryURL and NSHTTPURLResponse as arguments and expecting an instance of NSURL that points to a location on disk where you have write access.

Save a few more photos and return to the Downloads tab where you’ll see — only one file? Hey, what’s going on?

It turns out the filename isn’t unique, so you’ll need to implement your own naming logic. Replace the line just below the //2 comment in downloadPhoto() with the following code:

let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = {
  (temporaryURL, response) in
 
  if let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
    return directoryURL.URLByAppendingPathComponent("\(self.photoInfo!.id).\(response.suggestedFilename)")
  }
 
  return temporaryURL
}

Once again, let destination is a closure. But this time you implemented your own naming logic: you use the id of the photo captured from outside the closure and concatenate it with the file name suggested by the server, adding a “.” in between for separation.

Build and run, and now you can save multiple photos:

The file save functionality is now working well, but it would be nice to display some progress to the user as the file is downloading. Alamofire makes it easy to implement indicators showing the progress of the download.

Replace the line below // 3 in downloadPhoto() with the following:

// 4
let progressIndicatorView = UIProgressView(frame: CGRect(x: 0.0, y: 80.0, width: self.view.bounds.width, height: 10.0))
progressIndicatorView.tintColor = UIColor.blueColor()
self.view.addSubview(progressIndicatorView)
 
// 5
Alamofire.download(.GET, imageURL, destination).progress {
  (_, totalBytesRead, totalBytesExpectedToRead) in
 
  dispatch_async(dispatch_get_main_queue()) {
    // 6
    progressIndicatorView.setProgress(Float(totalBytesRead) / Float(totalBytesExpectedToRead), animated: true)
 
    // 7
    if totalBytesRead == totalBytesExpectedToRead {
      progressIndicatorView.removeFromSuperview()
    }
  }
}

Looking at each numbered comment in detail:

  1. You use a standard UIProgressView to show the progress of downloading a photo. Set it up and add it to the view hierarchy.
  2. With Alamofire you can chain .progress(), which takes a closure called periodically with three parameters: bytesRead, totalBytesRead, totalBytesExpectedToRead.
  3. Simply divide totalBytesRead by totalBytesExpectedToRead and you’ll get a number between 0 and 1 that represents the progress of the download task. This closure may execute multiple times if the if the download time isn’t near-instantaneous; each execution gives you a chance to update a progress bar on the screen.
  4. Once the download is finished, simply remove the progress bar from the view hierarchy.

Build and run your app; find another fantastic photo and save it to see the progress bar in action:

9

The progress bar disappears when the download completes, so you may not see it on particularly fast network connections.

Note that downloadPhoto is still using .resposneJSON() in section #1. Here’s a challenge to make sure you understand how response serializers work. Update the code above to use your generic response serializer . responseObject() instead. If you want to check your solution, you can see how we did it below.

Solution Inside: Solution SelectShow>

Optimizing and Refreshing

Okay, it’s time to implement the pull-to-refresh feature. (Have you been doing this instinctively in the app? You’re not alone! :])

Open PhotoBrowserCollectionViewController.swift, and replace func handleRefresh() with the following:

func handleRefresh() {
  refreshControl.beginRefreshing()
 
  self.photos.removeAllObjects()
  self.currentPage = 1
 
  self.collectionView.reloadData()
 
  refreshControl.endRefreshing()
 
  populatePhotos()
}

The code above simply empties your current All it does is empty your model (self.photos), reset the currentPage, and refresh the UI.

Build and run your app; go to the photo browser and pull to refresh; you should see the newest pictures from 500px.com show up in your app:

PullToRefresh

When you scroll quickly through the photo browser, you’ll notice that you can send cells off the screen whose image requests are still active. In fact, the image request still runs to completion, but the downloaded photo and associated data is just discarded.

Additionally, when you return to earlier cells you have to make a network request again for the photo — even though you just downloaded it a moment ago. You can definitely improve on this bandwidth-wasting design!

You’ll do this by caching retrieved images so they don’t have to be retrieved numerous times; as well, you’ll cancel any in-progress network requests if the associated cell is dequeued before the request completes.

Open PhotoBrowserCollectionViewController.swift and add the following code just above let refreshControl:

let imageCache = NSCache()

This creates an NSCache object that you will use to cache your images.

Next, replace collectionView(_:cellForItemAtIndexPath:) with the following:

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserCellIdentifier, forIndexPath: indexPath) as PhotoBrowserCollectionViewCell
 
  let imageURL = (photos.objectAtIndex(indexPath.row) as PhotoInfo).url
 
  // 1
  if cell.request?.request.URLString != imageURL {
    cell.request?.cancel()
  }
 
  // 2
  if let image = self.imageCache.objectForKey(imageURL) as? UIImage {
    cell.imageView.image = image
  } else {
    // 3
    cell.imageView.image = nil
 
    // 4
    cell.request = Alamofire.request(.GET, imageURL).validate(contentType: ["image/*"]).responseImage() {
      (request, _, image, error) in
      if error == nil && image != nil {
        // 5
        self.imageCache.setObject(image!, forKey: request.URLString)
 
        // 6
        if request.URLString == cell.request?.request.URLString {
          cell.imageView.image = image
        }
      } else {
        /*
        If the cell went off-screen before the image was downloaded, we cancel it and
        an NSURLErrorDomain (-999: cancelled) is returned. This is a normal behavior.
        */
      }
    }
  }
 
  return cell
}

Here’s a comment-by-comment view of what’s happening in the above code:

  1. The dequeued cell may already have an Alamofire request attached to it. Check if this request is relevant — that is, if the request’s URL matches the image URL the cell is supposed to show. If not, then cancel the request.
  2. Use optional binding to check if you have a cached version of this photo. If so, use the cached version instead of downloading it again.
  3. If you don’t have a cached version of the photo, download it. However, the the dequeued cell may be already showing another image; in this case, set it to nil so that the cell is blank while the requested photo is downloaded.
  4. Download the image from the server, but this time validate the content-type of the returned response. If it’s not an image, error will contain a value and therefore you won’t do anything with the potentially invalid image response. The key here is that you you store the Alamofire request object in the cell, for use when your asynchronous network call returns.
  5. If you did not receive an error and you downloaded a proper photo, cache it for later.
  6. Check that the cell hasn’t been dequeued to show a new photo; if not, the set the cell’s image accordingly.

Build and run your app; you’ll note that as you scroll back and forth in the photo browser that your images load a lot faster; you’ve optimized your network requests by pruning unnecessary requests and caching downloaded photos for re-use. Smart use of bandwidth and a snappy user interface make your users very happy, indeed! :]

Note: Some of the cells may appear empty but show up in the full screen view when you tap on them. This isn’t your fault; 500px.com doesn’t have thumbnail versions of some of the images.

Where to Go From Here?

Here’s the completed project from this tutorial series. You’ll see that it has more comments on the UI elements that weren’t covered in the tutorial.

If you followed along with both parts of the tutorial, you now have a good understanding of the most common use cases of networking with Alamofire. You learned about chainable request/response methods, built your own response serializers, created a router and encoded URLs with URL parameter encoding, downloaded a file to disk, and used a progress closure and validated responses. That’s quite a list of achievements! :]

Alamofire can also authenticate with servers using different schemes; as well, it can upload files and streams which weren’t covered in this tutorial. But with your new-found knowledge of Alamofire, learning about these features should be an easy task.

Alamofire is currently not as fully-featured as AFNetworking, but if you’re starting a new project in Swift, using Alamofire is much more enjoyable and covers most common networking use cases. UIKit extensions — one of the most popular features of AFNetworking — are noticeably absent from Alamofire but the two libraries can peacefully co-exist in the same project.

If you’ve used AFNetworking before and can’t live without the setImageWithURL set of category methods on UIKit, you may need to keep using AFNetworking in your projects. For example, you can use Alamofire for your server API calls and then use AFNetworking to display images asynchronously. AFNetworking has a shared cache, so you won’t need to manually cache or cancel your requests.

You can take your tutorial project to the next level in a couple of different ways; you could do away with the consumer key by authenticating with the server using a username and password so you can vote on your favorite photos on 500px.com, or you could add some pagination to the Comments controller to make it a little more friendly. I’d love to see what you come up with!

I hope you enjoyed this tutorial series; if you have any comments or questions about Alamofire or the project in this tutorial, please join the discussion below!

Intermediate Alamofire Tutorial is a post from: Ray Wenderlich

The post Intermediate Alamofire Tutorial appeared first on Ray Wenderlich.

Video Tutorial: WatchKit Part 3: Actions and Outlets

Beginning iOS Collection Views in Swift: Part 1/2

$
0
0
Create your own grid-based photo browsing app with collection views!

Create your own grid-based photo browsing app with collection views!

Update note: This tutorial was updated for Swift and iOS 8 by Richard Turton. Original post by Brandon Trebitowski.

The iOS Photos app has a stylish way of displaying photos via a multitude of layouts. You can view your photos in a nice grid view:

iOS Photos App

Or you can view your albums as stacks:

iOS Photos app in album view

You can even transition between the two layouts with a cool pinch gesture. “Wow, I want that in my app!”, you may think.

UICollectionView makes adding your own custom layouts and layout transitions (like those in the Photos app) simple to build.

You’re by no means limited to stacks and grids, because collection views are extremely customizable. You can use them to make circle layouts, cover-flow style layouts, Pulse news style layouts – almost anything you can dream up!

The good news is, if you’re familiar with UITableView, you’ll have no problem picking up collection views – using them is very similar to the table view data source and delegate pattern.

In this tutorial, you’ll get hands-on experience with UICollectionView by creating your own grid-based photo browsing app. By the time you are done with this tutorial, you will know the basics of using collection views and will be ready to start using this amazing technology in your apps!

Anatomy of a UICollectionView

Let’s go right to an example of the finished project. The UICollectionView contains several key components, as you can see below:

View of collection view app with parts highlighted

Take a look at these components one-by-one:

  1. UICollectionView – the main view in which the content is displayed, similar to a UITableView. Like a table view, a collection view is a UIScrollView subclass.
  2. UICollectionViewCell – similar to a UITableViewCell in a table view. These cells make up the content of the view and are added as subviews to the colleciton view. Cells can be created programmatically or inside Interface Builder.
  3. Supplementary Views – if you have extra information you need to display that shouldn’t be in the cells but still somewhere within the collection view, you should use supplementary views. These are commonly used for headers or footers.

Note:
Collection views can also have Decoration Views – if you want to add some extra views to enhance the appearance of the collection view (but don’t really contain useful data), you should use decoration views. Background images or other visual embellishments are good examples of decoration views. You won’t be using decoration views in this tutorial as it requires you to write a custom layout class.

In addition to the above visual components, a collection view has a layout object which is responsible for the size, position and several other attributes of the content. Layout objects are subclasses of UICollectionViewLayout. Layouts can be swapped out during runtime and the collection view can even automatically animate switching from one layout to another!

You can subclass UICollectionViewLayout to create your own custom layouts, but Apple has graciously provided developers with a basic “flow-based” layout called UICollectionViewFlowLayout. It lays elements out one after another based on their size, quite like a grid view. You can use this layout class out of the box, or subclass it to get some interesting behavior and visual effects.

You will learn more about these elements in-depth throughout this tutorial and the next. But for now, it’s time for you to get your hands into the mix with a project!

Introducing FlickrSearch

In the rest of this tutorial, you are going to create a cool photo browsing app called FlickrSearch. It will allow you to search for a term on the popular photo sharing site Flickr, and it will download and display any matching photos in a grid view, as you saw in the screenshot earlier.

Ready to get started? Fire up Xcode and go to File\New\Project… and select the iOS\Application\Single View Application template.

Xcode choosing single view template

This template will provide you with a simple UIViewController and storyboard to start out with, and nothing more. It’s a good “almost from scratch” point to start from.

Click Next to fill out the information about the application. Set the Product Name to FlickrSearch, the device type to iPad and the language to Swift. Click Next to select the project location, and then click Create.

Xcode project setup

The view controller subclass and storyboard that come with the single view application template aren’t any use to you – you’re going to use a UICollectionViewController, which, like a UITableViewController, is a specialized view controller subclass designed to host a collection view. Delete ViewController.swift and remove the empty view controller from Main.storyboard. Now you’ve got a really blank slate :].

Open AppDelegate.swift and add a constant to hold a delightful shade I call Wenderlich Green. Add the following underneath the import UIKit line:

let themeColor = UIColor(red: 0.01, green: 0.41, blue: 0.22, alpha: 1.0)

Use Wenderlich Green as a theme color for the whole app. Update application(_:didFinishLaunchingWithOptions) to the following:

func application(application: UIApplication!, didFinishLaunchingWithOptions
  launchOptions: NSDictionary!) -> Bool {
 
  window?.tintColor = themeColor
  return true
}

Starting your collection

Open Main.storyboard and drag in a Collection View Controller. Go to Editor\Embed in\Navigation Controller to create a navigation controller and automatically set the collection view controller as the root.

You should now have a layout like this in the storyboard:

UICollectionViewController embedded in a navigation controller

Select the collection view and set the background colour to white using the Attributes inspector:

Setting the collection view's background color

Note also in this screenshot that the Layout is set to Flow – this means the flow layout mentioned earlier will be used.

Note: Wondering what the Scroll Direction property does? This property is specific to UICollectionViewFlowLayout, and defaults to Vertical. A vertical flow layout means the layout class will place items from left to right across the top of the view until it reaches the view’s right edge, at which point it moves down to the next line. If there are two many elements to fit in the view at once, the user will be able to scroll vertically to see more.

Conversely, a horizontal flow layout places items from top to bottom across the left edge of the view until it reaches the bottom edge. Users would scroll horizontally to see items that don’t fit on the screen. In this tutorial, you’ll stick with the more common Vertical collection view.

Select the single cell in the collection view and set the Reuse Identifier to FlickrCell using the attributes inspector. This should also be familar from table views – the data source will use this identifier to dequeue or create new cells.

Drag in a text field to the center of the navigation bar above the collection view. This will be where the user enters their search text. Set the Placeholder Text of the search field to Search and the Return Key to Search in the attributes inspector, then control-drag from the text field to the collection view controller and choose the delegate outlet:

Setting the text field's delegate

UICollectionViewController does a lot, but you generally need to make a subclass. Do this now. Go to File\New\File…, choose Cocoa Touch Class and name the new class FlickrPhotosViewController. Make it a subclass of UICollectionViewController. There’s a lot of code added by the template, but the best way to understand what this class does is to start from scratch. Open FlickrPhotosViewController.swift and delete everything except the class declaration and import of UIKit. Your file should look like this:

import UIKit
 
class FlickrPhotosViewController : UICollectionViewController {
 
}

Inside the class definition, add a constant to match the reuse identifier you specified in the storyboard:

private let reuseIdentifier = "FlickrCell"

You’ll be filling in the rest of the gaps as you progress through the tutorial.

Go back to Main.storyboard, select the collection view controller and in the identity inspector, set the Class to FlickrPhotosViewController to match your new class:

Setting the custom class of a collection view controller

Fetching Flickr Photos

You first task for this section is to say the section title ten times fast. OK, just kidding.

Flickr is a wonderful image sharing service that has a publicly accessible and dead- simple API for developers to use. With the API you can search for photos, add photos, comment on photos, and much more.

To use the Flickr API, you need an API key. If you are doing a real project, I recommend you sign up for one here: http://www.flickr.com/services/api/keys/apply/.

However, for test projects like this, Flickr has a sample key they rotate out every so often that you can use without having to sign up. Simply perform any search at: http://www.flickr.com/services/api/explore/?method=flickr.photos.search and copy the API key out of the URL at the bottom – it follows the “&api_key=” all the way to the next “&”. Paste it somewhere in a text editor for later use.

For example, if the URL is:

http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=6593783 efea8e7f6dfc6b70bc03d2afb&format=rest&api_sig=f24f4e98063a9b8ecc8b522b238 d5e2f

Then the API key is: 6593783efea8e7f6dfc6b70bc03d2afb

Note: If you use the sample API key, note that it is changed periodically. So if you’re doing this tutorial over the course of several days, you might find that you have to get a new API key every so often. For this reason it might be easier to get an API key of your own from Flickr if you think you’re going to spend several days on this project.

Since this tutorial is about UICollectionView and not the Flickr API, I have created a set of classes for you that abstracts the Flickr search code. You can download them here.

Unzip and drag FlickrSearcher.swift into your project, making sure that the box Copy items into destination group’s folder (if needed) is checked, and click Finish.

The file contains two classes and a struct:

  • FlickrSearchResults: A struct which wraps up a search term and the results found for that search.
  • FlickrPhoto: Data about a photo retrieved from Flickr – its thumbnail, image, and metadata information such as its ID. There are also some methods to build Flickr URLs and some size calculations. FlickrSearchResults contains an array of these objects.
  • Flickr: Provides a simple block-based API to perform a search and return a FlickrSearchResult

Feel free to take a look at the code – it’s pretty simple and might inspire you to make use of Flickr in your own projects!

Before you can search Flickr, you need to enter an API key. Open FlickrSearcher.swift and replace the value of apiKey with the API key you obtained earlier. It should look something like this:

let apiKey = "hh7ef5ce0a54b6f5b8fbc36865eb5b32"

When you’re ready to go, move on to the next section – it’s time to do a little prep work before hooking into Flickr.

Preparing Data Structures

You’re going to build this project so that after each time you perform a search, it displays a new “section” in the collection view with the results (rather than simply replacing the previous section). In other words, if you search for “ninjas” and then “pirates”, there will be a section of ninjas and a section of pirates in the table view. Talk about a recipe for disaster!

To accomplish this, you’re going to need to need a data structure so you can keep the data for each section separate. An array of FlickrSearchResults will do the trick nicely.

Open FlickrPhotosViewController.swift and add a few properties and a convenience method:

private var searches = [FlickrSearchResults]()
private let flickr = Flickr()
 
func photoForIndexPath(indexPath: NSIndexPath) -> FlickrPhoto {
  return searches[indexPath.section].searchResults[indexPath.row]
}

searches is an array that will keep track of all the searches made in the app, and flickr is a reference to the object that will do the searching for you.

photoForIndexPath is a convenience method that will get a specific photo related to an index path in your collection view. You’re going to access a photo for a specific index path a lot, and you don’t want to repeat code.

Getting Good Results

You are now ready to get your Flickr search on! You want to trigger a search when the user hits Search after typing in a query. You already connected the text field’s delegate outlet to your collection view controller, now you can do something about it.

Open FlickrPhotosViewController.swift and add UITextFieldDelegate to the declaration:

class FlickrPhotosViewController: UICollectionViewController, UITextFieldDelegate {

Then add the relevant delegate method:

// MARK : UITextFieldDelegate
 
func textFieldShouldReturn(textField: UITextField!) -> Bool {
  // 1
  let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
  textField.addSubview(activityIndicator)
  activityIndicator.frame = textField.bounds
  activityIndicator.startAnimating()
  flickr.searchFlickrForTerm(textField.text) {
    results, error in
 
    //2
    activityIndicator.removeFromSuperview()
    if error != nil {
      println("Error searching : \(error)")
    }
 
    if results != nil {
      //3
      println("Found \(results!.searchResults.count) matching \(results!.searchTerm)")
      self.searches.insert(results!, atIndex: 0)
 
      //4
      self.collectionView.reloadData()
    }
  }
 
  textField.text = nil
  textField.resignFirstResponder()
  return true
}

Here is an explanation of the code:

  1. After adding an activity view, use the Flickr wrapper class I provided to search Flickr for photos that match the given search term asynchronously. When the search completes, the completion block will be called with a the result set of FlickrPhoto objects, and an error (if there was one).
  2. Log any errors to the console. Obviously, in a production application you would want to display these errors to the user.
  3. The results get logged and added to the front of the searches array
  4. At this stage, you have new data and need to refresh the UI. You’re using the insertSections method to add your results at the top of the list.

Go ahead and run your app. Perform a search in the text box, and you should see a log message in the console indicating the number of search results, similar to this:

Found 20 matching bananas

Note that the results are limited to 20 by the Flickr class to keep load times down.

Unfortunately, you don’t see any photos in your collection view! Just like a table view, a collection view doesn’t do much unless you implement the relevant data source and delegate methods.

Feeding the UICollectionView

As you probably already know, when you use a table view you have to set a data source and a delegate in order to provide the data to display and handle events (like row selection).

Similarly, when you use a collection view you have to set a data source and a delegate as well. Their roles are the following:

  • The data source (UICollectionViewDataSource) returns information about the number of items in the collection view and their views.
  • The delegate (UICollectionViewDelegate) is notified when events happen such as cells being selected, highlighted, or removed.

UICollectionViewFlowLayout also has a delegate protocol – UICollectionViewDelegateFlowLayout. It allows you to tweak the behaviour of the layout, configuring things like the cell spacing, scroll direction, and more.

In this section, you’re going to implement the required UICollectionViewDataSource and UICollectionViewDelegateFlowLayout methods on your view controller, so you are all set up to work with your collection view. The UICollectionViewDelegate methods aren’t needed for this part, but you’ll be using them in part 2.

UICollectionViewDataSource

In FlickrPhotosViewController.swift, add the following datasource methods:

// MARK: UICollectionViewDataSource
 
//1
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    return searches.count
}
 
//2
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return searches[section].searchResults.count
}
 
//3
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as UICollectionViewCell
    cell.backgroundColor = UIColor.blackColor()
    // Configure the cell
    return cell
}

These methods are pretty straightforward:

  1. There’s one search per section, so the number of sections is the count of the searches array.
  2. The number of items in a section is the count of the searchResults array from the relevant FlickrSearch object.
  3. This is a placeholder method just to return a blank cell – you’ll be populating it later. Note that collection views require you to have registered a cell with a reuse identifier, or a runtime error will occur.

Build and run again, and perform a search. You should see 20 new cells, albeit looking a little dull at the moment:

Boring black cells

UICollectionViewFlowLayoutDelegate

As I mentioned early in the section, every collection view has an associated layout. You’re using the pre-made flow layout for this project, since it’s nice and easy to use and gives you the grid-view style you’re looking for.

Still in FlickrPhotosViewController.swift, update the class declaration to indicate that it will conform to the flow layout delegate protocol:

class FlickrPhotosViewController:
  UICollectionViewController,
  UITextFieldDelegate,
  UICollectionViewDelegateFlowLayout {

Add the following methods:

// MARK: UICollectionViewDelegateFlowLayout
 
//1
func collectionView(collectionView: UICollectionView!,
  layout collectionViewLayout: UICollectionViewLayout!,
  sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize {
 
  let flickrPhoto =  photoForIndexPath(indexPath)
  //2
  if var size = flickrPhoto.thumbnail?.size {
    size.width += 10
    size.height += 10
    return size
  }
  return CGSize(width: 100, height: 100)
}
 
//3
private let sectionInsets = UIEdgeInsets(top: 50.0, left: 20.0, bottom: 50.0, right: 20.0)
 
func collectionView(collectionView: UICollectionView!,
  layout collectionViewLayout: UICollectionViewLayout!,
  insetForSectionAtIndex section: Int) -> UIEdgeInsets {
  return sectionInsets
}
  1. collectionView(_:layout:sizeForItemAtIndexPath:) is responsible for telling the layout the size of a given cell. To do this, you must first determine which FlickrPhoto you are looking at, since each photo could have different dimensions.
  2. Here, optional binding is used to determine which size should be returned. The thumbnail property of FlickrPhoto is an optional, which means it may not exist. If that is the case, a default size is returned. If the thumbnail does exist, padding is added to the size to give a border to the image.
  3. collectionView(_:layout:insetForSectionAtIndex:) returns the spacing between the cells, headers, and footers. A constant is used to store the value.

Build and run again, and perform a search. Behold! Black squares of different sizes!

Slightly less boring black cells

With this infrastructure in place, you are now ready to actually display some photos on screen!

Creating custom UICollectionViewCells

One of the great things about UICollectionView is that, like table views, it is easy to set up collection views visually in the Storyboard editor. You can drag and drop collection views into your view controller, and design the layout for your cells right from within the Storyboard editor! Let’s see how it works.

Open Main.storyboard and select the collection view. Give yourself a bit of room to work by setting the cell size to 200×200 in the size inspector:

Setting a cell's size

Note: Setting this size doesn’t affect the cells in your app, because you’ve implemented the delegate method to give a size for each cell, which overwrites anything set in the storyboard.

Drag an image view onto the cell. It will automatically fill the cell. Remember the border padding you added in the layout delegate method above? You need to add autolayout constraints so that the image view is always inset from the edges of the cell, whatever size it is. With the image view selected, open the pin menu and add constraints of 5 points all the way round. Untick Prefer margins relative.

Adding constraints to size the image view

UICollectionViewCell doesn’t allow for much customization beyond changing the background color. You will almost always want to create your own subclass, to allow you to easily access any content subviews you have added.

Go to File\New\File… and choose Cocoa Touch Class. Call the new class FlickrPhotoCell and make it a subclass of UICollectionViewCell.

Open Main.storyboard and select the cell. In the identity inspector, set the cell’s class to FlickrPhotoCell:

Setting the identity of a custom cell

Open the Assistant editor, making sure it is displaying FlickrPhotoCell.swift and control-drag from the image view to the class to add a new outlet:

Adding an image view outlet

Now you have a custom cell class with an image view. It’s time to put a photo on it! Open FlickrPhotosViewController.swift and replace collectionView(cellForItemAtIndexPath:) with the following:

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  //1
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as FlickrPhotoCell
  //2
let flickrPhoto = photoForIndexPath(indexPath)
  cell.backgroundColor = UIColor.blackColor()
  //3
  cell.imageView.image = flickrPhoto.thumbnail
 
  return cell
}

This is a little different from the placeholder method you defined earlier.

  1. The cell coming back is now a FlickrPhotoCell
  2. You need to get the FlickrPhoto representing the photo to display, using the convenience method from earlier
  3. You populate the image view with the thumbnail

Build and run, perform a search and you’ll finally see those banana pictures you’ve been searching for!

Project showing actual photos

Yes! Success! Notice that each photo fits perfectly inside its cell, with the cell echoing the photo’s dimensions. The credit is due to the work you did inside of sizeForItemAtIndexPath to tell the cell size to be the size of the photo plus 10 points, as well as the Auto Layout settings you modified.

Note: If your view doesn’t look like this or the photos are acting weird, it likely means your Auto Layout settings aren’t correct. If you get stuck, try comparing your settings to the solution for this project.

At this point, you’ve now got a complete working (and quite cool) example of UICollectionView – give yourself a pat on the back! It should look something like this completed sample project.

Where To Go From Here?

But there’s more! Stay tuned for part 2 of this tutorial, where you will learn:

  • How to add custom headers to collection views
  • How to implement single cell selection to bring up a detail view
  • How to implement multi-cell selection too!

In the meantime, if you have any questions or comments on what you’ve learned so far, please join the forum discussion below!

Beginning iOS Collection Views in Swift: Part 1/2 is a post from: Ray Wenderlich

The post Beginning iOS Collection Views in Swift: Part 1/2 appeared first on Ray Wenderlich.

Beginning iOS Collection Views in Swift: Part 2/2

$
0
0
Create your own grid-based photo browsing app with collection views!

Create your own grid-based photo browsing app with collection views!

Update note: This tutorial was updated for Swift and iOS 8 by Richard Turton. Original post by Brandon Trebitowski.

In the first part of this tutorial, you saw how to use a UICollectionView to display a grid of photos.

In this second and final part of the tutorial, you will continue the journey and learn how to interact with a collection view as well as customize it a bit further with headers. You’ll continue working where you left off in part 1 so open up your project or download the completed project from part 1 and start from there (though you’ll still need to get a new API key as shown in part 1).

Adding a header

The app has one section per set of search results. It would be nice to add a header before each set of search results, to give the user a bit more context about the photos.

You will create this header using a class called UICollectionReusableView. This class is kind of like a collection view cell (in fact, cells inherit from this class), but used for other things like headers or footers.

This view can be built inside of your storyboard and connected to its own class. Start off by adding a new file via File\New\File…, select the iOS\Cocoa Touch\Objective-C class template and click Next. Name the class FlickrPhotoHeaderView and make it a subclass of UICollectionReusableView. Click Next and then Create to save the file.

Open up MainStoryboard.storyboard and click on the collection view inside of the Scene Inspector on the left (you might need to drill down a couple of levels from the main view first). Open up the Attributes Inspector and check the Section Header box under Accessories:

If you look at the scene inspector on the left, a “Collection Reusable View” has automatically been added under the Collection View. Click on the Collection Reusable View to select it, and you can begin adding subviews.

To give you a little more space to work with, click the white handle at the bottom of the view and drag it down, making the view 90 pixels tall. (Or, you can set the size for the view explicitly via the Size Inspector.)

Drag a label into the header view and center it using the guides. Change its Font to System 32.0, then go to the alignment menu and pin it to the horizontal and vertical centers of the container, and update the frame:

Aligning a label to the center of it's superview

Select the header view itself, open the identity inspector and set the Class to FlickrPhotoHeaderView.

Open the Attributes Inspector and set the Background to 90% white, and set the Identifier to FlickrPhotoHeaderView. This is the identifier that will be used when dequeuing this view.

Open the Assistant editor, making sure FlickrPhotoHeaderView.swift is open, and control-drag from the label to the class to make a new outlet. Call it label:

class FlickrPhotoHeaderView: UICollectionReusableView {
  @IBOutlet weak var label: UILabel!
}

If you build and run the app at this point, you still won’t see a header (even if it is just a blank one with the word “Label”). There’s another datasource method you need to implement. Open FlickrPhotosViewController.swift and add the following method:

override func collectionView(collectionView: UICollectionView,
  viewForSupplementaryElementOfKind kind: String,
  atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    //1
    switch kind {
      //2
      case UICollectionElementKindSectionHeader:
        //3
        let headerView =
        collectionView.dequeueReusableSupplementaryViewOfKind(kind,
          withReuseIdentifier: "FlickrPhotoHeaderView",
          forIndexPath: indexPath)
          as FlickrPhotoHeaderView
        headerView.label.text = searches[indexPath.section].searchTerm
        return headerView
      default:
        //4
        assert(false, "Unexpected element kind")
    }
}

This method is similar to cellForItemAtIndexPath, but for supplementary views. Here’s a step-by-step explanation of the code:

  1. The kind parameter is supplied by the layout object and indicates which sort of supplementary view is being asked for.
  2. UICollectionElementKindSectionHeader is a supplementary view kind belonging to the flow layout. By checking that box in the storyboard to add a section header, you told the flow layout that it needs to start asking for these views. There is also a UICollectionElementKindSectionFooter, which you’re not currently using. If you don’t use the flow layout, you don’t get header and footer views for free like this.
  3. The header view is dequeued using the identifier added in the storyboard. This works just like cell dequeuing. The label’s text is then set to the relevant search term.
  4. An assert is placed here to make it clear to other developers (including future you!) that you’re not expecting to be asked for anything other than a header view.

This is a good spot to build and run. You will see that your UI is mostly complete. If you do multiple searches, you’ll get nice section headers dividing up your results. As a bonus, try rotating the device – notice how the layout, including the headers, adapts perfectly, without any extra work required :]

Collection view showing section header

Of course, black and white cabbages are what you’d expect from this search?

Interacting With Cells

In this final section of the tutorial you will learn some ways to interact with collection view cells. You’ll take two different approaches. The first will display a larger version of the image. The second will demonstrate how to support multiple selection in order to share images.

Single selection

Collection views can animate changes to their layout. Your first task is to show a larger version of a photo when it is tapped.

First, you need to add a property to keep track of the tapped cell. Open FlickrPhotosViewController.swift and add the following code:

//1
var largePhotoIndexPath : NSIndexPath? {
didSet {
  //2
  var indexPaths = [NSIndexPath]()
  if largePhotoIndexPath != nil {
    indexPaths.append(largePhotoIndexPath!)
  }
  if oldValue != nil {
    indexPaths.append(oldValue!)
  }
  //3
  collectionView!.performBatchUpdates({
    self.collectionView.reloadItemsAtIndexPaths(indexPaths)
    }) {
      completed in
      //4
      if self.largePhotoIndexPath != nil {
        self.collectionView.scrollToItemAtIndexPath(
          self.largePhotoIndexPath!,
          atScrollPosition: .CenteredVertically,
          animated: true)
      }
    }
  }
}

Here’s the step by step breakdown:

  1. largePhotoIndexPath is an optional that will hold the index path of the tapped photo, if there is one.
  2. Whenever this property gets updated, the collection view needs to be updated. a didSet property observer is the safest place to manage this. There may be two cells that need reloading, if the user has tapped one cell then another, or just one if the user has tapped the first cell, then tapped it again to shrink.
  3. performBatchUpdates will animate any changes to the collection view performed inside the block. You want it to reload the affected cells.
  4. Once the animated update has finished, it’s a nice touch to scroll the enlarged cell to the middle of the screen

“What enlarged cell?”, I hear you asking. You’ll get to that in a minute!

Tapping a cell will make the collection view select it. You want to know a cell has been tapped, so you can set the largeIndexPath property, but you don’t actually want to select it, because that might get confusing later on when you’re doing multiple selection. UICollectionViewDelegate has you covered. The collection view asks its delegate if it’s OK to select a specific cell. Still in FlickrPhotosViewController.swift, add the following code:

override func collectionView(collectionView: UICollectionView,
  shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
 
  if largePhotoIndexPath == indexPath {
    largePhotoIndexPath = nil
  }
  else {
    largePhotoIndexPath = indexPath
  }
  return false
}

This method is pretty simple. If the tapped cell is already the large photo, set the largePhotoIndexPath property to nil, otherwise set it to the index path the user just tapped. This will then call the property observer you added earlier and cause the collection view to reload the affected cell(s).

To make the tapped cell appear larger, you need to modify the sizeForItemAtIndexPath flow layout delegate method. Replace the existing code with this:

func collectionView(collectionView: UICollectionView!,
  layout collectionViewLayout: UICollectionViewLayout!,
  sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize {
 
  let flickrPhoto = photoForIndexPath(indexPath)
 
  // New code
  if indexPath == largePhotoIndexPath {
    var size = collectionView.bounds.size
    size.height -= topLayoutGuide.length
    size.height -= (sectionInsets.top + sectionInsets.right)
    size.width -= (sectionInsets.left + sectionInsets.right)
    return flickrPhoto.sizeToFillWidthOfSize(size)
  }
  // Previous code
  if var size = flickrPhoto.thumbnail?.size {
    size.width += 10
    size.height += 10
    return size
  }
  return CGSize(width: 100, height: 100)
}

You’ve added the code highlighted in the comments. This calculates the size of the cell to fill as much of the collection view as possible whilst maintaining its aspect ratio.

There’s not much point in making a bigger cell unless you have a larger photo to show in it.

Open Main.storyboard and drag an activity indicator into the image view in the collection view cell. In the Attributes inspector, set the Style to Large White and check the Hides When Stopped box. Drag the indicator into the middle of cell (let the guides show you where that is) then, using the Alignment Tool button, horizontally and vertically center the indicator in it’s container.

Cell contents including activity indicator centred in the superview

Open the assistant editor and control-drag from the activity indicator to FlickrPhotoCell.swift to add an outlet – call it activityIndicator:

  @IBOutlet weak var activityIndicator: UIActivityIndicatorView!

Also in FlickrPhotoCell.swift, add the following code to give the cell control of its background color. You’ll need this later on:

override func awakeFromNib() {
  super.awakeFromNib()
  self.selected = false
}
 
override var selected : Bool {
  didSet {
    self.backgroundColor = selected ? themeColor : UIColor.blackColor()
  }
}

Finally, you need to update cellForItemAtIndexPath back in FlickrPhotosCollectionViewController.swift:

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
 
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
    reuseIdentifier, forIndexPath: indexPath) as FlickrPhotoCell
  let flickrPhoto = photoForIndexPath(indexPath)
 
  //1
  cell.activityIndicator.stopAnimating()
 
  //2
  if indexPath != largePhotoIndexPath {
    cell.imageView.image = flickrPhoto.thumbnail
    return cell
  }
 
  //3
  if flickrPhoto.largeImage != nil {
    cell.imageView.image = flickrPhoto.largeImage
    return cell
  }
 
  //4
  cell.imageView.image = flickrPhoto.thumbnail
  cell.activityIndicator.startAnimating()
 
  //5
  flickrPhoto.loadLargeImage {
    loadedFlickrPhoto, error in
 
    //6
    cell.activityIndicator.stopAnimating()
 
    //7
    if error != nil {
      return
    }
 
    if loadedFlickrPhoto.largeImage == nil {
      return
    }
 
    //8
    if indexPath == self.largePhotoIndexPath {
      if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? FlickrPhotoCell {
        cell.imageView.image = loadedFlickrPhoto.largeImage
      }
    }
  }
 
  return cell
}

This is quite a long method now, so here’s the step-by-step:

  1. Always stop the activity spinner – you could be reusing a cell that was previously loading an image
  2. This part is as before – if you’re not looking at the large photo, just set the thumbnail and return
  3. If the large image is already loaded, set it and return
  4. By this point, you want the large image, but it doesn’t exist yet. Set the thumbnail image and start the spinner going. The thumbnail will stretch until the download is complete
  5. Ask for the large image from Flickr. This loads the image asynchronously and has a completion block
  6. The load has finished, so stop the spinner
  7. If there was an error or no photo was loaded, there’s not much you can do.
  8. Check that the large photo index path hasn’t changed while the download was happening, and retrieve whatever cell is currently in use for that index path (it may not be the original cell, since scrolling could have happened) and set the large image.

Build and run, perform a search and tap a nice-looking photo – it grows to fill the screen, and the other cells move around to make space!

Collection view with large image

You look bigger to him as well.

Tap the cell again, or try scrolling and tapping a different cell. You didn’t have to write any code to move or animate those cells, the collection view and its layout object did all the hard work for you!

Multiple selection

Your final task for this tutorial is to let the user select multiple photos and share them with a friend. The process for multi-selection on a collection view is very similar to that of a table view. The only trick is to tell the collection view to allow multiple selection.

The process for selection works in the following way:

  1. The user taps the Share button to tell the UICollectionView to allow multi- selection and set the sharing property to YES.
  2. The user taps multiple photos that they want to share, adding them to an array.
  3. The user taps the Share button again, which brings up the sharing interface.
  4. When the user finishes sharing the images or taps Cancel, the photos are deselected and the collection view goes back to single selection mode.

First, add the following code in FlickrPhotosViewController.swift:

private var selectedPhotos = [FlickrPhoto]()
private let shareTextLabel = UILabel()
 
func updateSharedPhotoCount() {
  shareTextLabel.textColor = themeColor
  shareTextLabel.text = "\(selectedPhotos.count) photos selected"
  shareTextLabel.sizeToFit()
}

The selectedPhotos array will keep track of the photos the user has selected, and the shareTextLabel will provide feedback to the user on how many photos have been selected. You will call updateSharedPhotoCount to keep shareTextLabel up to date.

Next, (also in FlickrPhotosViewController.swift) create the property that will hold the sharing state:

var sharing : Bool = false {
  didSet {
    collectionView.allowsMultipleSelection = sharing
    collectionView.selectItemAtIndexPath(nil, animated: true, scrollPosition: .None)
    selectedPhotos.removeAll(keepCapacity: false)
    if sharing && largePhotoIndexPath != nil {
      largePhotoIndexPath = nil
    }
 
    let shareButton =
      self.navigationItem.rightBarButtonItems!.first as UIBarButtonItem
    if sharing {
      updateSharedPhotoCount()
      let sharingDetailItem = UIBarButtonItem(customView: shareTextLabel)
      navigationItem.setRightBarButtonItems([shareButton,sharingDetailItem], animated: true)
    }
    else {
      navigationItem.setRightBarButtonItems([shareButton], animated: true)
    }
  }
}

sharing is a Bool with another property observer, similar to largePhotoIndexPath above. In this observer, you toggle the multiple selection status of the collection view, clear any existing selection, and empty the selected photos array. You also update the bar button items to include and update the label added above.

Open Main.storyboard and drag a UIBarButtonItem to the right of the navigation bar above the collection view controller. In the Attributes Inspector, set the Identifier to Action to give it the familiar sharing icon. Open the assistant editor, making sure FlickrPhotosViewController.swift is open, and control-drag from the bar button into the class to create a new action. Call the action share:

Fill in the action method as shown:

@IBAction func share(sender: AnyObject) {
  if searches.isEmpty {
    return
  }
 
  if !selectedPhotos.isEmpty {
    // TODO
  }
 
  sharing = !sharing
}

At the moment, all this method does is toggle the sharing state, kicking off all the changes in the property observer method added earlier.

You actually want to allow the user to select cells now, so update shouldSelectItemAtIndexPath to take this into account. Add the following code to the top of the method:

if (sharing) {
  return true
}

This will allow selection in sharing mode.

Implement the delegate method to add selected photos to the shared photos array and update the label:

override func collectionView(collectionView: UICollectionView,
  didSelectItemAtIndexPath indexPath: NSIndexPath) {
  if sharing {
    let photo = photoForIndexPath(indexPath)
    selectedPhotos.append(photo)
    updateSharedPhotoCount()
  }
}

And remove them when the cell is deselected (tapped again):

override func collectionView(collectionView: UICollectionView!,
  didDeselectItemAtIndexPath indexPath: NSIndexPath!) {
  if sharing {
    if let foundIndex = find(selectedPhotos, photoForIndexPath(indexPath)) {
      selectedPhotos.removeAtIndex(foundIndex)
      updateSharedPhotoCount()
    }
  }
}

Build and run, and perform a search. Tap the share button to go into sharing mode and select different photos. The label will update and the selected cells will get a fetching Wenderlich Green border.

Multiple cells selected in a collection view

I don’t know what I was expecting this search to show up, but it wasn’t this

If you tap the share button again, everything just gets deselected, and you go back into non-sharing mode, where tapping a single photo enlarges it.

Of course, this share button isn’t terribly useful unless there’s actually a way to share the photos! Replace the TODO comment in your share method with the following code:

var imageArray = [UIImage]()
for photo in self.selectedPhotos {
  imageArray.append(photo.thumbnail!);
}
 
let shareScreen = UIActivityViewController(activityItems: imageArray, applicationActivities: nil)
let popover = UIPopoverController(contentViewController: shareScreen)
popover.presentPopoverFromBarButtonItem(self.navigationItem.rightBarButtonItems!.first as UIBarButtonItem,
  permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)

First, this code creates an array of UIImage objects from the FlickrPhoto‘s thumbnails. The UIImage array is much more convenient, as we can simply pass it to a UIActivityViewController. The UIActivityViewController will show the user any image sharing services or actions available on the device: iMessage, Mail, Print, etc. You simply present your UIActivityViewController from within a popover (because this is an iPad app), and let the user take care of the rest!

Build and run, enter sharing mode, select some photos and hit the share button again. Your share dialog will appear!

Share Screen

Note: Testing exclusively on a simulator? You will find the simulator has far fewer sharing options than on device. If you are having trouble confirming that your share screen is properly sharing your images, try the Save (x) Images option. Whether on device or on simulator, this will save the selected images to Photos app, where you can review them to ensure everything worked.

Where To Go From Here?

Here is the complete project that you developed in the tutorial series.

Congratulations, you have finished creating your very own stylish Flickr photo browser, complete with a cool UICollectionView based grid view!

In the process, you learned how to make custom UICollectionViewCells, create headers with UICollectionReusableView, detect when rows are tapped, implement multi-cell selection, and much more!

If you have any questions or comments about UICollectionViews or this tutorial, please join the forum discussion below!

Beginning iOS Collection Views in Swift: Part 2/2 is a post from: Ray Wenderlich

The post Beginning iOS Collection Views in Swift: Part 2/2 appeared first on Ray Wenderlich.

iOS 6 by Tutorials Third Edition Now Available!

$
0
0
iOS 6 by Tutorials fully updated for Xcode 6 and iOS 8!

iOS 6 by Tutorials fully updated for Xcode 6 and iOS 8!

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

In this new third edition, all of the tutorials in the book have been fully updated to Xcode 6 and iOS 8.

In addition to making sure everything still works in iOS 8, we added notes and information about relevant iOS 8 changes where appropriate.

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

Note that this is the last time we will be updating iOS 6 by Tutorials. After all, it wouldn’t make sense for us to continue updating this all the way to iOS 20! It’s been a great ride and we hope this book has been useful for you during your iOS journey.

Wondering where the iOS 7 by Tutorials update is? We’re working on that now and it’s almost done – stay tuned.

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

We hope you enjoy the new third edition!

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

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


Video Tutorial: WatchKit Part 4: Segues

Happy Thanksgiving 2014!

$
0
0
Thank you!

Thank you!

In the United States, it’s Thanksgiving. On this day, we do two very important things:

  • Stuff ourselves with food :]
  • Give thanks for wonderful people in our lives!

I like to take this day to thank the awesome folks who make everything we do on the site possible, and reflect back on the previous year.

So take a look at how things have changed since last year!

The raywenderlich.com Team

This blog started out in 2010 as my personal blog:

raywenderlich.com Team - 2010

In 2011, we brought in an amazing initial crew (many of which are still around!):

rw_map_2011

In 2012 and 2013, we expanded the team even further, including our first employee:

rw_map_2012-2013

Finally, this year we continued to expand, including hiring two of our best editors:

2014_rwteam_map

As you can see, although this site bears my name, I am only a small part of what makes this happen.

All credit goes to this amazing team of authors, editors, forum subject matter experts, and updaters who work hard every day to make great tutorials for you!

I feel incredibly grateful to be able to work with such a talented and hard working crew. I hope to continue to work (and play!) with you for many years to come – thank you! :]

And you!

I’m also incredibly thankful to each and every one of you who read this site!

Even though I haven’t met all of you, I feel like I am directly connected to you through the words I write here. This site represents many moments of my life, and you’ve been with me along the way – and I’m incredibly grateful for that.

Here are our stats for this year:

  • Consistently released 2+ free tutorials per week (up from 1-2 last year).
  • Over 600 tutorials on the site (up from 300 last year).
  • About 2.7 million pageviews per month (up from 2 million last year).
  • Over 50K newsletter subscribers (up from 30K last year).
  • 10 popular books and starter kits for sale on our site (up from 8 last year).
  • New video tutorial section, and our upcoming tutorial conference!

These stats truly baffle my mind!

But the thing to remember is these aren’t just numbers – every reader or subscriber is a real person and you all have lives, families, friends, and dreams. Whether you started reading this blog four years ago, or just started reading today, thank you so much!

Remember that without you, this blog would just be me, writing an occasional post here and there. So again, huge thanks to each and every one of you – you have made all of this possible!

Where Will We Go From Here?

We’ve accomplished a great deal in a year, but there are many more adventures ahead!

Imagine this site as a 16-bit RPG adventurer – he may have cleared the first dungeon, but there’s much more leveling up ahead ;]

Again, a huge Happy Thanksgiving to the best developer community on the web – I love you guys *sniff*! :]

Happy Thanksgiving 2014! is a post from: Ray Wenderlich

The post Happy Thanksgiving 2014! appeared first on Ray Wenderlich.

Video Tutorial: WatchKit Part 5: Passing Data with Segues

Intro to Object-Oriented Design in Swift: Part 1/2

$
0
0
Object-oriented design

Object-oriented programmers love vehicle hierarchies.

Update note: This tutorial was updated for iOS 8 and Swift by Ray Fix. Original post by Tutorial Team member Ellen Shapiro.

A huge piece of the programming puzzle in working with Cocoa, Objective-C and Swift is Object-Oriented Programming. Almost all modern programming languages use this approach, and wrapping your head around its concepts and patterns can be incredibly helpful when reading and writing code.

Underneath the relation between UITableView and UIScrollView or UIView and UIButton are the fundamental concepts of object-oriented design. By understanding these concepts you’ll have a better grasp on why things are organized the way they are in Cocoa and Cocoa Touch, and you’ll be a more thoughtful programmer when writing your own apps and frameworks.

In this series you’ll learn more about object-oriented design, including the following concepts:

  • The Basics Of Objects
  • Inheritance
  • Model-View-Controller
  • Polymorphism
  • Common Object-Oriented Patterns

This series is designed for developers who are fairly new to programming overall. You likely haven’t worked with other languages extensively, and you’re not quite sure why everything is being done in a particular way.

This tutorial will cover object-oriented design principles rather than specific syntax, so you should already know the basics of Swift and Xcode before reading on. If you need a refresher on the basics, check out the Quick Start Tutorial.

Getting Started

In order to try and understand some of these concepts in a more concrete manner, you’ll build an application called Vehicles. This uses one of the most common metaphors for translating real-world items into virtual objects: the “vehicle”, which could be a bicycle, a car, or really anything with wheels.

For instance, this is a vehicle:

Kleinwagen_16

But so is this:

Motorcycle on a white background

Or this:

B

Or this:

European 18-wheeler with canvas trailer

In this part of the tutorial, you’ll create a data model using basic object-oriented techniques to represent all of these vehicles, and a simple application which implements the data model and displays vehicle data to the user.

Download the starter project, which contains a basic framework for the application you’ll use to learn about Object-Oriented Programming.

The Basics Of Objects

In object-oriented programming, the basic goal is to break down the characteristics of a “thing” to create an object or objects that describe what that thing is and what that thing does.

Sometimes, as with vehicles, your “thing” has a real-world equivalent. Sometimes it doesn’t, as with the many different types of UIViewController objects. For the sake of simplicity, you’ll start out by creating objects that have real-world analogs.

In order to answer the question of what a “thing” is, you have to first determine what its defining characteristics are.

Other languages will refer to this as a “field”, a “member”, or even just a “variable”. However, in Swift the defining characteristics of an object are shown by its properties.

Think about the generic concept of a “vehicle” for a moment — something that describes all of the photos above. What common characteristics of a “vehicle” spring to mind?

  • It has a number of wheels.
  • It has some sort of power source, whether human, gas, electric, or hybrid, which makes it move.
  • It has a brand*, like Ford, Chevy, Harley-Davidson, or Schwinn.
  • It has a model name like Mustang, Corvette, Sportster, or Fastback.
  • It has a year associated with its manufacture.

*- this is sometimes referred to in cars and trucks as a “Make”, but we’ll refer to it as a “brand” across the board for clarity.

Now that you have the basic characteristics of a vehicle, you can create an Object with these characteristics.

The file Vehicle.swift defines a class of objects named Vehicle.

Open Vehicle.swift and add the following code within the class braces:

var brandName = "null"
var modelName = "null"
var modelYear = 0
var powerSource = "null"
var numberOfWheels = 0

These property declarations describe the characteristics of the object you want to keep track of. For now you are just providing placeholder values so that the Swift compiler can infer the types as a String or Int. (Later, in Part 2, you will add a real initializer to get rid of these dummy, placeholder values. This is why you are not using Swift Optionals to represent them.)

Minor Digression: Initialization and Properties

One of the important safety features of Swift is how it enables and enforces proper initialization of objects. This means that it’s virtually impossible to use uninitialized memory. The above properties are called stored properties and must be initialized either by setting them explicitly as you have done, or by initializing them in an init() method. If you supply all of your stored properties with values, the compiler will won’t require you to explicitly set them in the init() method.

Minor Digression: Class versus Struct

In Swift, you can define an object-like thing with the keyword class or struct. In this tutorial, you will be using class to define objects because these allow you to build class hierarchies and override methods. Structs, while very useful for many things, are more static in nature and don’t allow for this. For more information on when to use a class versus a struct see The Swift Programming Language Book.

Describing the Object

On to the second critical question for every object — what exactly does the object do?

A programmatic description of what an object does is almost universally called a method. Think about the common actions of the vehicles in the photos above:

  • It can go forward
  • It can go backward
  • It can stop
  • It can turn
  • It can change gears
  • It can make some sort of noise (e.g. a horn or a bell)

Most often, you’d be using methods that don’t return anything. However, to make it a little easier to display what’s happening in your app, you’re going to use some methods that return String objects.

A Minor Digression: Class Methods vs. Instance Methods

You’ve probably noticed when writing code that some methods have the class keyword in front of them. These indicate that a method is a Class method as opposed to a normal Instance method.

The simplest way to think of the difference is like a schematic blueprint in the physical world: There’s only one blueprint for something, but using that blueprint, you can make many different copies.

A class method is an action that can be performed with that blueprint, but without creating a specific copy of the object from that blueprint. For example, UIColor has the class method blackColor() that returns a new instance of UIColor set to black.

An instance method, requires a specific copy of the object created using that blueprint to perform any action. For example, the String instance "Hello There" has an instance method lowercaseString that would return "hello there". A class method lowercaseString wouldn’t make any sense since that’s just the blueprint for a string – there’s no actual text to lower case!

Adding Basic Methods to Your Class

Still in Vehicle.swift, add the following methods to your Vehicle class.

func goForward() -> String {
  return "null"
}
 
func goBackward() -> String {
  return "null"
}
 
func stopMoving() -> String {
  return "null"
}
 
func turn(degrees:Int) -> String {
  var normalizedDegrees = degrees
 
  //Since there are only 360 degrees in a circle, calculate what a single turn would be.
  let degreesInACircle = 360
 
  if (normalizedDegrees > degreesInACircle || normalizedDegrees < -degreesInACircle) {
    // The % operator returns the remainder after dividing.
    normalizedDegrees = normalizedDegrees % degreesInACircle
  }
 
  return String(format: "Turn %d degrees.", normalizedDegrees)
}
 
func changeGears(newGearName:String) -> String {
  return String(format: "Put %@ into %@ gear.",self.modelName, newGearName)
}
 
func makeNoise() -> String {
  return "null"
}

Most of this code is just the skeleton of setting up the methods; you’ll fill in the implementation details later. The turn() and changeGears() methods have some logging output too, which will help you test that your methods are working before you continue any further in the tutorial.

Open AppDelegate.swift, and replace the implementation of application(_:didFinishLaunchingWithOptions:) with the following:

func application(application: UIApplication, 
                 didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
 
  var vehicle = Vehicle()
  // Test methods with implementations
  println("Vehicle turn: \(vehicle.turn(700))")
  var changeGearResult = vehicle.changeGears("Test")
  println("Vehicle change gears: \(changeGearResult)")
 
  // Test methods without implementations
  println("Vehicle make noise: \(vehicle.makeNoise())")
  println("Vehicle go forward: \(vehicle.goForward())")
  println("Vehicle go backward: \(vehicle.goBackward())")
  println("Vehicle stop moving: \(vehicle.stopMoving())")
 
  return true
}

Once the vehicle instance is instantiated, you’ll call each of the instance methods and log the output to see what they do.

Build and run your app; you’ll see that all implemented strings return proper logs with the appropriate items filled in. Since you have put in placeholder values for most of the properties, you will see this in the debug console:

Debugger

You’ll be using Inheritance to provide more specific implementations of each of these methods.

Inheritance

The basic concept of inheritance is similar to that of genetics: children inherit the characteristics of their parents.
However, it’s a lot more strict than real world genetics in a single-inheritance language like Swift. Rather than having two parents from whom you inherit a mix of characteristics, “child” classes, or subclasses, inherit all the characteristics of their “parent” classes, or superclasses.

To see inheritance in action, create a subclass of Vehicle called Car. Go to File\New\File… and select iOS\Source\Swift File. Name the file Car.

Now open Car.swift and add the following class definition for Car:

class Car : Vehicle { 
  override init() {
    super.init()
    numberOfWheels = 4
  }
}

Car derives from your Vehicle class and inherits all of its methods and properties. Note that it makes perfect sense to say, “A Car is-a Vehicle“. If you can naturally say, “Subclass is-a Superclass”, derivation usually makes sense.

Since you provided values for all of the properties in the superclass, there’s no need to explicitly set each in your subclass. You may only want to set certain properties and leave the others as their defaults. For Cars, we know that the numberOfWheels will be four. To express this you override the initializer, call the superclass initializer, and then customize the numberOfWheels property to four. The order is significant. If you called super.init() last, numberOfWheels would get reset back to 0. Fortunately, the Swift complier will not allow you to make this mistake.

But what if you need more information to describe the car? Cars have more specific characteristics than just the number of wheels How many doors does it have? Is it a hatchback or does it have a normal trunk? Is it a convertible? Does it have a sunroof?

Well, you can easily add those new properties! In Car.swift, above your initializer, add:

var isConvertible:Bool = false
var isHatchback:Bool = false
var hasSunroof:Bool = false
var numberOfDoors:Int = 0

Now, any Car object can be customized with all properties from the Vehicle and Car classes.

Overriding Methods

Now that you’ve added the appropriate additional properties, you can override several methods from the superclass to provide full implementations of those methods.

Overriding a method means “taking a method declared in the superclass and creating your own implementation.” 

For example, when you add a new UIViewController object, it already comes with overridden methods for viewDidLoad and others. In Swift, when you override a method, you must use the override keyword. This communicates your intent to the compiler. If you accidentally misspell a method parameter, for example, the compiler can let you know that something is wrong. Also, if you didn’t know you were overriding a method, the compiler will also let you know.

Unlike an initializer method, when you override a normal method, you can do one of two things:

  1. Include a call to the super.method() to take advantage of everything happening higher up the inheritance chain, or
  2. Provide your own implementation from scratch.

In all of the UIViewController methods, you can tell that Apple wants you to call the [super method] – there’s some important stuff in there that needs to execute before your UIViewController subclass can do its work.

However, since most of the methods you’re going to override in the Car class are returning empty values, you can just create your own implementations. There’s nothing useful in the superclass’s implementation so there’s no need to call it.

Still in Car.swift add the following private helper method to simplify your superclass override:

// MARK: - Private method implementations
private func start() -> String {
  return String(format: "Start power source %@.", powerSource)
}

Some vehicles such as bicycles don’t need to be started, but cars do! Here you use the access control keyword private to express the fact that this method should not be used outside of this file.

Note: Although this tutorial does not cover access control in detail, using it is pretty simple. If you do not add any access control keywords, the class, method or property defaults to internal and can be used by any code in the same module. If you use private, access is limited to the file. If you use public, you can access from anywhere, including outside the module. This is useful if you are building a framework, to be used by other projects, for example.

Next, add the remaining superclass overrides:

// MARK: - Superclass Overrides
override func goForward() -> String {
  return String(format: "%@ %@ Then depress gas pedal.", start(), changeGears("Forward"))
}
 
override func goBackward() -> String {
  return String(format: "%@ %@ Check your rear view mirror. Then depress gas pedal.", start(), changeGears("Reverse"))
}
 
override func stopMoving() -> String {
  return String(format: "Depress brake pedal. %@", changeGears("Park"))
}
 
override func makeNoise() -> String {
  return "Beep beep!"
}

Now that you have a concrete, or fully implemented, subclass of Vehicle, you can start building out your Table View controller.

Building out the User Interface

Open VehicleListTableViewController.swift and add the following method just after viewDidLoad:

// MARK: - Data setup
func setupVehicleArray() {
  // Clear the array. (Start from scratch.)
  vehicles.removeAll(keepCapacity: true)
 
  // Create a car.
  var mustang = Car()
  mustang.brandName = "Ford"
  mustang.modelName = "Mustang"
  mustang.modelYear = 1968
  mustang.isConvertible = true
  mustang.isHatchback = false
  mustang.hasSunroof = false
  mustang.numberOfDoors = 2
  mustang.powerSource = "gas engine"
 
  // Add it to the array
  vehicles.append(mustang)
 
  // Create another car.
  var outback = Car()
  outback.brandName = "Subaru"
  outback.modelName = "Outback"
  outback.modelYear = 1999
  outback.isConvertible = false
  outback.isHatchback = true
  outback.hasSunroof = false
  outback.numberOfDoors = 5
  outback.powerSource = "gas engine"
 
  // Add it to the array.
  vehicles.append(outback)
 
  // Create another car
  var prius = Car()
  prius.brandName = "Toyota"
  prius.modelName = "Prius"
  prius.modelYear = 2002
  prius.hasSunroof = true
  prius.isConvertible = false
  prius.isHatchback = true
  prius.numberOfDoors = 4
  prius.powerSource = "hybrid engine"
 
  // Add it to the array.
  vehicles.append(prius)
 
  // Sort the array by the model year
  vehicles.sort { $0.modelYear < $1.modelYear }
}

This simply adds a data setup method to construct your vehicle array. Each Car is created and customized based on the values set on its properties.

In viewDidLoad() add the following after its super.viewDidLoad():

setupVehicleArray()
title = "Vehicles"

The above method executes just after your view loads from the Storyboard. It calls the setupVehicleArray() method you just created, and sets the title of the VehicleListTableViewController to reflect its contents.

Build and run your application; you’ll see something like the following:
Car Build

Your table view is populated with three entries of “Vehicle.Car”. Vehicle is the name of the module (app, in this case) and Car is the name of the class.

The good news is that these objects are being recognized as Car objects. The bad news is that what’s being displayed isn’t terribly useful. Take a look at what’s set up in the UITableViewDataSource method tableView(_:cellForRowAtIndexPath:):

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
 
  let vehicle = vehicles[indexPath.row] as Vehicle
  cell.textLabel?.text = "\(vehicle)"
  return cell
}

Here, you’re grabbing a UITableViewCell object then getting the Vehicle at the index in the self.vehicles array matching the row of the cell you’re constructing. Next, you set that specific Vehicle’s string representation as the text for the cell’s textLabel.

The default string produced method isn’t very human-friendly. You’ll want to define a method in Vehicle that describes what each Vehicle object represents in a way that is easy for your users to understand.

Go to Vehicle.swift and add the following property below your other properties:

// MARK: - Computed  Properties
 
var vehicleTitle: String {
  return String(format:"%d %@ %@", modelYear, brandName, modelName)
}

Until now, you have been using stored properties to keep track of attributes associated with your objects. This is an example of a computed property. This is a read-only property that isn’t backed by a variable; instead, it runs the code inside braces and generates a fresh string every time.

Go back to VehicleListTableViewController.swift and update tableView(_:cellForRowAtIndexPath:) to use this new property on Vehicle as follows:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
 
  let vehicle = vehicles[indexPath.row] as Vehicle
  cell.textLabel?.text = vehicle.vehicleTitle
  return cell
}

Build and run your application; it should look a little nicer now:

Three Vehicles With Readable Titles

However, if you select a Vehicle from the list, all you’ll see are the same elements visible in the storyboard, with none of the details from the Vehicle you selected:

Before Data Hookup Detail

What’s up with that?

Open VehicleDetailViewController.swift; you’ll see that while the UI was constructed in the Storyboard and all the @IBOutlets are already hooked up to save you some time fighting with AutoLayout, none of the data is hooked up.

Hooking Your Data to Your View

To hook up the data, go to VehicleDetailViewController.swift and fill in the method configureView() to take advantage of the vehicle set by the segue as follows:

func configureView() {
  // Update the user interface for the detail item.
  if let vehicle = detailVehicle {
    title = vehicle.vehicleTitle
 
    var basicDetails = "Basic vehicle details:\n\n"
    basicDetails += "Brand name: \(vehicle.brandName)\n"
    basicDetails += "Model name: \(vehicle.modelName)\n"
    basicDetails += "Model year: \(vehicle.modelYear)\n"
    basicDetails += "Power source: \(vehicle.powerSource)\n"
    basicDetails += "# of wheels: \(vehicle.numberOfWheels)\n"
 
    detailDescriptionLabel?.text = basicDetails
  }
}

Build and run your application; select a vehicle from the table view and you’ll see that the detail view is now showing the correct title and details, as so:

Basic vehicle details

Model-View-Controller Encapsulation of Logic

iOS and many other modern programming languages have a design pattern known as Model-View-Controller , or MVC for short.

The idea behind MVC is that views should only care about how they are presented, models should only care about their data, and controllers should work to marry the two without necessarily knowing too much about their internal structure.

The biggest benefit to the MVC pattern is making sure that if your data model changes, you only have to make changes once.

One of the biggest rookie mistakes that programmers make is putting far too much of the logic in their UIViewController classes. This ties views and UIViewControllers too closely to one particular type of model, making it harder to reuse views to display different kinds of details.

Why would you want to do implement the MVC pattern in your app? Well, imagine that you wanted to add more specific details about a car to VehicleDetailViewController. You could start by going back into configureView() and adding some information specifically about the car, as so:

// Car-specific details
basicDetails += "\n\nCar-Specific Details:\n\n"
basicDetails += "Number of doors: \(vehicle.numberOfDoors)"  // ERROR

But you’ll notice that this does not work because the compiler doesn’t know what the property numberOfDoors is. That property is associated with the Car subclass and not part of the Vehicle base class.

There are a couple of ways you can fix this.

One way is to have the view controller attempt to downcast the vehicle to a Car type using the as? Car operation. If it succeeds, access the Car properties and display them.

Any time you catch yourself downcasting, ask yourself: “is my view controller trying to do too much?”

In this case, the answer is yes. You can take advantage of inheritance to use the same method to supply the string to be displayed for the appropriate details for each subclass. This way the view controllers doesn’t need to concern itself with the details of specific subclasses.

Creating Subclasses via Inheritance

First, go to Vehicle.swift and add a new computed property to get a details string:

var vehicleDetails:String {
  var details = "Basic vehicle details:\n\n"
  details += "Brand name: \(brandName)\n"
  details += "Model name: \(modelName)\n"
  details += "Model year: \(modelYear)\n"
  details += "Power source: \(powerSource)\n"
  details += "# of wheels: \(numberOfWheels)\n"
  return details
}

The method is similar to what you added to VehicleDetailViewController.swift, except it returns the generated string rather than display it somewhere directly.

Now, you can use inheritance to take this basic vehicle string and add the specific details for the Car class. Open Car.swift and add the override the property vehicleDetails:

override var vehicleDetails:String {
  // Get basic details from superclass
  let basicDetails = super.vehicleDetails
 
  // Initialize mutable string
  var carDetailsBuilder = "\n\nCar-Specific Details:\n\n"
 
  // String helpers for booleans
  let yes = "Yes\n"
  let no = "No\n"
 
  // Add info about car-specific features.
  carDetailsBuilder += "Has sunroof: "
  carDetailsBuilder += hasSunroof ? yes : no
 
  carDetailsBuilder += "Is Hatchback: "
  carDetailsBuilder += isHatchback ? yes : no
 
  carDetailsBuilder += "Is Convertible: "
  carDetailsBuilder += isConvertible ? yes : no
 
  carDetailsBuilder += "Number of doors: \(numberOfDoors)"
 
  // Create the final string by combining basic and car-specific details.
  let carDetails = basicDetails + carDetailsBuilder
 
  return carDetails
}

Car’s version of the method starts by calling the superclass’s implementation to get the vehicle details. It then builds the car-specific details string into carDetailsBuilder and then combines the two at the very end.

Now go back to VehicleDetailViewController.swift and replace configureView() with the much simpler implementation below to display this string that you’ve created:

func configureView() {
  // Update the user interface for the detail item.
  if let vehicle = detailVehicle {
    title = vehicle.vehicleTitle
    detailDescriptionLabel?.text = vehicle.vehicleDetails
  }
}

Build and run your application; select one of the cars, and you should now be able to see both general details and car-specific details as shown below:

Basic and car-specific details

Your VehicleDetailViewController class now allows the Vehicle and Car classes to determine the data to be displayed. The only thing VehicleDetailsViewController is doing is connecting that information up with the view!

The real power in this is evident when you create further subclasses of Vehicle. Start with a fairly simple one for a motorcycle.

Go to File\New\File… and select iOS\Source\Swift File. Name the file Motorcycle. Inside Motorcycle.swift, create a new subclass of Vehicle called Motorcycle as shown below:

class Motorcycle : Vehicle {
 
}

Since motorcycles can have either a deep booming engine noise, or a high-pitched whine of an engine noise, each Motorcycle object you create should specify which type of noise it makes. Add the following property and method to your new class:

var engineNoise = ""
 
override init() {
  super.init()
  numberOfWheels = 2
  powerSource = "gas engine"
}

Since all motorcycles have two wheels and are gas-powered (for the sake of this example, anything that’s electric-powered would be considered a scooter, not a motorcycle), you can set up the number of wheels and power source when the object is instantiated.

Next, add the following methods to override the superclass methods.

// MARK: - Superclass Overrides
override func goForward() -> String {
  return String(format: "%@ Open throttle.", changeGears("Forward"))
}
 
override func goBackward() -> String {
  return String(format: "%@ Walk %@ backwards using feet.", changeGears("Neutral"), modelName)
}
 
override func stopMoving() -> String {
  return "Squeeze brakes"
}
 
override func makeNoise() -> String {
  return self.engineNoise
}

Finally, override the vehicleDetails property in order to add the Motorcycle-specific details to the vehicleDetails as shown below:

override var vehicleDetails:String {
  //Get basic details from superclass
  let basicDetails = super.vehicleDetails
 
  //Initialize mutable string
  var motorcycleDetailsBuilder = "\n\nMotorcycle-Specific Details:\n\n"
 
  //Add info about motorcycle-specific features.
  motorcycleDetailsBuilder += "Engine Noise: \(engineNoise)"
 
  let motorcycleDetails = basicDetails + motorcycleDetailsBuilder
 
  return motorcycleDetails
}

Now, it’s time to create some instances of Motorcycle.

Open VehicleListTableViewController.swift, find setupVehicleArray() and add the following code below the Cars you’ve already added but above where you sort the array:

// Create a motorcycle
var harley = Motorcycle()
harley.brandName = "Harley-Davidson"
harley.modelName = "Softail"
harley.modelYear = 1979
harley.engineNoise = "Vrrrrrrrroooooooooom!"
 
// Add it to the array.
vehicles.append(harley)
 
// Create another motorcycle
var kawasaki = Motorcycle()
kawasaki.brandName = "Kawasaki"
kawasaki.modelName = "Ninja"
kawasaki.modelYear = 2005
kawasaki.engineNoise = "Neeeeeeeeeeeeeeeeow!"
 
// Add it to the array
self.vehicles.append(kawasaki)

The above code simply instantiates two Motorcycle objects and adds them to the vehicles array.

Build and run your application; you’ll see the two Motorcycle objects you added in the list:

Added Motorcycles

Tap on one of them, and you’ll be taken to the details for that Motorcycle, as shown below:

Motorcycle Details

Whether it’s a car or a motorcycle (or even a plain old vehicle), you can call vehicleDetails and get the relevant details.

By using proper separation between the model, the view, and the view controller and inheritance, you’re now able to display data for several subclasses of the same superclass without having to write tons of extra code to handle different subclass types. Less code written == happier developers! :]

Housing Logic in the Model Class

You can also use this approach to keep some of the more complicated logic encapsulated within the model class. Think about a Truck object: many different types of vehicles are referred to as a “truck”, ranging from pickup trucks to tractor-trailers. Your truck class will have a little bit of logic to change the truck’s behavior based on the number of cubic feet of cargo it can haul.

Go to File\New\File… and select iOS\Source\Swift File. Name the file Truck. Inside Truck.swift, create a new subclass of Vehicle called Truck as you did for Car and Motorcycle:

Add the following integer property to Truck.swift to hold the truck’s capacity data:

class Truck : Vehicle {
  var cargoCapacityCubicFeet: Int = 0
}

Since there are so many different types of trucks, you don’t need to create an initializer method that provides any of those details automatically. You can simply override the superclass methods which would be the same no matter the size of the truck.

//MARK: - Superclass overrides
override func goForward() -> String {
  return String(format:"%@ Depress gas pedal.", changeGears("Drive"))
}
 
override func stopMoving() -> String {
  return String(format:"Depress brake pedal. %@", changeGears("Park"))
}

Next, you’ll want to override a couple of methods so that the string returned is dependent on the amount of cargo the truck can haul. A big truck needs to sound a backup alarm, so you can create a private method.

Add the following private method code to your Truck class:

// MARK: - Private methods
private func soundBackupAlarm() -> String {
  return "Beep! Beep! Beep! Beep!"
}

Then back in the superclass overrides section, you can use that soundBackupAlarm() method to create a goBackward() method that sounds the alarm for big trucks:

override func goBackward() -> String {
  if cargoCapacityCubicFeet > 100 {
    // Sound a backup alarm first
    return String(format:"Wait for \"%@\", then %@", soundBackupAlarm(), changeGears("Reverse"))
  } else {
    return String(format:"%@ Depress gas pedal.", changeGears("Reverse"))
  }
}

Trucks also have very different horns; a pickup truck, for instance, would have a horn similar to that of a car, while progressively larger trucks have progressively louder horns. You can handle this with a switch statement in the makeNoise() method.

Add makeNoise() as follows:

override func makeNoise() -> String {
  switch numberOfWheels {
  case Int.min...4:
    return "Beep beep!"
  case 5...8:
    return "Honk!"
  default:
    return "HOOOOOOOOONK!"
  }
}

Finally, you can override the vehicleDetails property in order to get the appropriate details for your Truck object. Add the method as follows:

override var vehicleDetails: String {
  // Get basic details from superclass
  let basicDetails = super.vehicleDetails
 
  // Initialize mutable string
  var truckDetailsBuilder = "\n\nTruck-Specific Details:\n\n"
 
  // Add info about truck-specific features.
  truckDetailsBuilder += "Cargo Capacity: \(cargoCapacityCubicFeet) cubic feet"
 
  // Create the final string by combining basic and truck-specific details.
  let truckDetails = basicDetails + truckDetailsBuilder
 
  return truckDetails
}

Now that you’ve got your Truck object set up, you can create a few instances of it. Head back to VehicleListTableViewController.swift, find the setupVehicleArray() method and add the following code right above where you sort the array:

// Create a truck
var silverado = Truck()
silverado.brandName = "Chevrolet"
silverado.modelName = "Silverado"
silverado.modelYear = 2011
silverado.numberOfWheels = 4
silverado.cargoCapacityCubicFeet = 53
silverado.powerSource = "gas engine"
 
// Add it to the array
vehicles.append(silverado)
 
// Create another truck
var eighteenWheeler = Truck()
eighteenWheeler.brandName = "Peterbilt"
eighteenWheeler.modelName = "579"
eighteenWheeler.modelYear = 2013
eighteenWheeler.numberOfWheels = 18
eighteenWheeler.cargoCapacityCubicFeet = 408
eighteenWheeler.powerSource = "diesel engine"
 
// Add it to the array
vehicles.append(eighteenWheeler)

This will create a couple of Truck objects with the truck-specific properties to join the cars and motorcycles in the array.

Build and run you application; select one of the Trucks to verify that you can now see the appropriate Truck-specific details, as demonstrated below:

Truck-specific Details

That looks pretty great! The truck details are coming through thanks to the vehicleDetails computed property, inheritance, and overridden implementations.

Where To Go From Here?

You can download a copy of the project up to this point.

You’ve set up a base Vehicle class with Car, Motorcycle, and Truck subclasses, all listed in the same table view. However, there’s no way to verify that all of your truck’s size-specific handling is working correctly.

Part 2 of this tutorial will fill out the rest of the app to display more vehicle details. Along the way, you’ll learn about polymorphism, proper initialization, and a couple of additional major design patterns for Object-Oriented Programming.

Got questions? Ask in the comments below!

Intro to Object-Oriented Design in Swift: Part 1/2 is a post from: Ray Wenderlich

The post Intro to Object-Oriented Design in Swift: Part 1/2 appeared first on Ray Wenderlich.

Intro to Object-Oriented Design in Swift: Part 2/2

$
0
0
Object-oriented design

Wheels, engines, movement…alike, yet different.

Update note: This tutorial was updated for iOS 8 and Swift by Ray Fix. Original post by Tutorial Team member Ellen Shapiro.

In Part 1 of this tutorial, you learned the basics of object-oriented design: objects, inheritance, and the model-view-controller pattern. You created the beginnings of a simple application called Vehicles to help you gain a better understanding of these concepts.

Here in the second part, you’re going to learn about polymorphism and initialization. Along the way, you will learn about a few of Object-Oriented programming patterns including the Decorator, the Adapter, and the Singleton.

If you completed the previous tutorial, great! You can pick up where you left off with your previous project. However, if you want to jump right into things, you can download the completed project from Part 1 to get started.

Polymorphism

The general definition of Polymorphism comes from its Greek roots – “Poly” means many, and “Morph” means forms.

The computer-science specific definition, pulled from the Free Online Dictionary of Computing, is:

A variable that may refer to objects whose class is not known at compile time and which respond at run time according to the actual class of the object to which they refer.

You have already seen polymorphism in action in Part 1. In VehicleDetailViewController’s configureView() method, you used the computed property vehicleDetails that was overridden in each subclass. You know that any kind of vehicle will have that vehicleDetails property, but you’ll get a different value depending on whether the object is a Car, Motorocycle or Truck.

There are several patterns related to polymorphism that can be used within Swift, but two key ones you’ll see often are the Decorator and Adapter patterns. These are implemented using the language keywords extension and protocol respectively.

The Decorator Pattern

From Apple’s Cocoa Fundamentals guide’s section on Cocoa Design Patterns:

The Decorator design pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. As does subclassing, adaptation of the Decorator pattern allows you to incorporate new behavior without modifying existing code. Decorators wrap an object of the class whose behavior they extend.

The primary example of the Decorator pattern in Swift is when you create an extension. In Objective-C, there is a similar mechanism with class categories.

Extensions allow you to add additional methods to classes and structs without having to subclass or alter the original source code. For example, they can be used to add functionality to the stock UIKit components such as additional helper methods on UIView or custom color constructors on UIColor.

The difference between an extension and a subclass is pretty simple: You can add new methods but not new properties with an extension. If you want to add a new property, you’ll probably need to create a subclass and use the power of inheritance to create your additional properties and methods.

But what if you don’t need to add new properties? What if you just want to create a simple way to encapsulate something you have to do repeatedly with a particular UIKit object? In this case, an extension is a possible solution.

To try this out, you’re going to add a convenience method to UIAlertController to do away with performing the setup dance required for simple alerts over and over again in your code.

Implementing the Decorator Pattern

You could put the definition of your UIAlertController extension in any file and it would be visible to the entire app. However, to keep things neat and to facilitate it being used in other future projects, create it in a separate file. Go to File\New\File… and select iOS\Source\Swift File. Then name the file UIAlertController+Convenience. The format [Component]+[Extension Name] borrows a page from Objective-C category file naming conventions. While this is just a convention, it helps communicate your intent. In this case, you are adding some convenience methods to UIAlertController.

Creating a method within an extension is very similar to creating a method on a normal class. Open UIAlertController+Convenience.swift and add the following:

import UIKit
 
extension UIAlertController {
  class func alertControllerWithTitle(title:String, message:String) -> UIAlertController {
    let controller = UIAlertController(title: title, message: message, preferredStyle: .Alert)
    controller.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
    return controller
  }  
}

Rather than defining a new class, you create an extension on the existing UIAlertController class.

You’re not doing anything revolutionary here — just packaging up and returning a UIAlertController that has a single button dismiss action.

Bonus: In addition to using the Decorator pattern by adding functionality to UIAlertController you are also implementing what is called the Factory pattern. The Factory pattern returns an initialized instance configured in a particular way. You might say this is a Decorated Factory. :]

Time to use your new extension. Open VehicleDetailViewController.swift. Towards the bottom of the file, you’ll find several methods marked @IBAction that are empty. Update goForward(), goBackward(), stopMoving() and makeNoise() as shown below to use your new extension:

@IBAction func goForward(sender: AnyObject) {
  if let vehicle = detailVehicle {
    let controller = UIAlertController.alertControllerWithTitle("Go Forward", message: vehicle.goForward())
    presentViewController(controller, animated: true) {}
  }
}
 
@IBAction func goBackward(sender: AnyObject) {
  if let vehicle = detailVehicle {
    let controller = UIAlertController.alertControllerWithTitle("Go Backward", message: vehicle.goBackward())
    presentViewController(controller, animated: true) {}
  }
}
 
@IBAction func stopMoving(sender: AnyObject) {
  if let vehicle = detailVehicle {
    let controller = UIAlertController.alertControllerWithTitle("Stop Moving", message: vehicle.stopMoving())
    presentViewController(controller, animated: true) {}
  }
}
 
@IBAction func makeNoise(sender: AnyObject) {
  if let vehicle = detailVehicle {
    let controller = UIAlertController.alertControllerWithTitle("Make Some Noise!", message: vehicle.makeNoise())
    presentViewController(controller, animated: true) {}
  }
}

The if let statement makes sure that vehicle exists, and if it does, creates an alert controller using your extension and presents it.

Build and run your application; after selecting a vehicle, press any button except the “Turn…” button, and you’ll see the appropriate message for each instance of a Vehicle. For example, if you press the “Make Some Noise!” button for various Vehicles, you’ll see the following:

Make some noise!

To implement the turn() method, you’ll need to pass some information from the UIAlertController back to your view controller. You can do this with Swift closures. You can think of a closure as an unnamed function similar to a block in Objective-C. First, go back to UIAlertController+Convenience.swift and add the following method:

class func alertControllerWithNumberInput(#title:String, message:String, buttonTitle:String, handler:(Int?)->Void) -> UIAlertController {
  let controller = UIAlertController(title: title, message: message, preferredStyle: .Alert)
 
  controller.addTextFieldWithConfigurationHandler { $0.keyboardType = .NumberPad }
 
  controller.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
 
  controller.addAction(UIAlertAction(title: buttonTitle, style: .Default) { action in
    let textFields = controller.textFields as? [UITextField]
    let value = textFields?[0].text.toInt()
    handler(value)
    } )
 
  return controller
}

This method configures a UIAlertController with a text field that takes numeric input from the keypad and returns it. When the button is pressed, it attempts to parse the text into an Int. The value is an optional type because parsing might fail. This value is passed to a handler function supplied as an argument to the function.

To use the extension, open VehicleDetailsViewController.swift file and add the implementation for turn()

@IBAction func turn(sender: AnyObject) {
  if let vehicle = detailVehicle {
    let controller = UIAlertController.alertControllerWithNumberInput(title: "Turn", message: "Enter number of degrees to turn:", buttonTitle: "Go!") {
      integerValue in
      if let value = integerValue {
        let controller = UIAlertController.alertControllerWithTitle("Turn", message: vehicle.turn(value))
        self.presentViewController(controller, animated: true, completion: nil)
      }
    }
    presentViewController(controller, animated: true) {}
  }
}

This sets a controller with numeric input is created via your extension. Your handler checks if you have a valid value and then throws up another alert view with the turn message.

Build and run your project; select a Vehicle from the list, tap the Turn button and enter a number of degrees to turn, like so:

Turn 90 degrees

If you hit Cancel, nothing will happen. However, if you hit Go!, the first UIAlertController disappears and the following UIAlertController will appear:

Turn complete!

Your application is now functionally complete. However, what if you want to make your code a little more elegant so it’s easier to maintain and add to it later? It’s time to refactor how your objects are initialized and learn about some more object-oriented design patterns to make your coding life easier!

Proper Initialization

One of the big safety features of Swift is its ability to do proper initialization. Through a process called two-phase initialization, the Swift complier guarantees that you never access uninitialized memory.

Swift uses two phase initialization that works roughly like the following. First, initialization of your Derived class begins.

Phase 1

  1. The stored properties of the most derived class are set either at the point where the stored property is declared or at the beginning of the init() method. In Phase 1, even properties declared with let can be modified.
  2. super.init() is called. The same rule above applies. That is, the super class also sets all of it’s stored properties that it introduces and calls up the chain.
  3. The process continues until the top of the inheritance hierarchy is finally reached.

Phase 2

  1. The highest level init() method returns starting the chain back down towards the most derived class
  2. As each derived class returns, it can overwrite stored properties defined in the super classes above it.
  3. This process continues back down to the most derived class.
  4. Finally, any convenience initializers in the chain have the option to customize the instance and to work with self. More on convenience initializers in the next section.

You can find a longer explanation of initialization in the Swift Programming Language Book. While the rules are a bit subtle, they are simple to follow and avoid corner cases where properties would not be initialized or confusingly over-written.

Designated vs Convenience Initializers

Every class must have at least one designated initializer that is responsible for initializing everything in that class. If you set all of the stored properties (or they have their own default initializers) the compiler will provide you with a default designated initializer automatically.

It is typical for a class to have only one designated initializer. Sometimes you can declare additional convenience initializers that are marked with the keyword convenience. While this tutorial doesn’t show any convenience initializers in action, they can be convenient (as the name suggests) in shortening common initialization calls.

Convenience initializers may only call the designated initializer or other convenience initializers of the same class. Ultimately they must eventually lead to the designated initializers of the same class. This simple rule ensures instances are in a usable state by the end of the initialization process.

Unlike convenience initializers, designated initializers of a derived class must only call the designated of the superclass.

Back to the Code

When you created Vehicle, Car, Motorcycle and Truck in Part 1 you used a bunch of placeholder values to set the properties. After creating Vehicle instances, you immediately set the values to something more reasonable. This, however, is an error prone process. It is easy to forget setting one of the values resulting in a bug. It would be better if you could set the values explicitly right at initialization so the compiler can check your work.

In this section you’ll set the values right in the initializer. This will make your classes easy to use correctly and conversely, difficult to use incorrectly.

Because you specified all of the initial values and didn’t write a default initializer, the compiler created one for you behind the scenes. When you write your own, the compiler-created one goes away. So, along with creating a new member-wise initializer, add a default initializer to prevent breakage while you are refactoring. Open Vehicle.swift and add these below your computed properties:

// Mark: - Initialization Methods
 
init() {}
 
init(brandName:String, modelName:String, modelYear:Int, powerSource:String, numberOfWheels:Int) {
  self.brandName = brandName
  self.modelName = modelName
  self.modelYear = modelYear
  self.powerSource = powerSource
  self.numberOfWheels = numberOfWheels
}

The blank initializer is just temporary to keep the compiler from complaining. You will remove this later.

The new initializer with arguments simply sets each property with the value coming in.

Now open Car.swift. Change the declaration of the four stored properties to remove their default values. They should look like the following:

let isConvertible: Bool
let isHatchback: Bool
let hasSunroof: Bool
let numberOfDoors: Int

Next, replace the current overridden init() with the following:

init(brandName: String, modelName: String, modelYear: Int, powerSource: String,
  isConvertible: Bool, isHatchback: Bool, hasSunroof: Bool, numberOfDoors: Int) {
 
    self.isConvertible = isConvertible
    self.isHatchback = isHatchback
    self.hasSunroof = hasSunroof
    self.numberOfDoors = numberOfDoors
 
    super.init(brandName: brandName, modelName: modelName, modelYear: modelYear,
      powerSource: powerSource, numberOfWheels: 4)
}

This initializes a Car object in one call. In the initializer you first initialize the properties that the Car class introduces. Then you call the superclass’s designated initializer. By declaring your stored properties with the let keyword, they are now immutable so they can never change after they are initialized. Using let is a good idea because it more appropriately models these properties. The number of doors on a car, for example, does not change from minute to minute.

Now repeat the process for Motorcycle. Open Motorcycle.swift and replace the stored property and overridden init() with the following:

let engineNoise: String
 
init(brandName: String, modelName: String, modelYear: Int, engineNoise: String) {
  self.engineNoise = engineNoise
  super.init(brandName: brandName, modelName: modelName, modelYear: modelYear,
    powerSource: "gas engine", numberOfWheels: 2)
}

And the some thing for Truck. Open Truck.swift and replace the stored property and init with the following:

let cargoCapacityCubicFeet: Int
 
init(brandName: String, modelName: String, modelYear: Int, powerSource: String, numberOfWheels: Int, cargoCapacityInCubicFeet:Int) {
  self.cargoCapacityCubicFeet = cargoCapacityInCubicFeet
  super.init(brandName: brandName, modelName: modelName, modelYear: modelYear,
    powerSource: powerSource, numberOfWheels: numberOfWheels)
}

Finally you are ready to remove the temporary init() with no parameters in the Vehicle class and change all of the properties to immutable values with no dummy placeholders. Go to Vehicle.swift and change the stored properties to the following:

let brandName: String
let modelName: String
let modelYear: Int
let powerSource: String
let numberOfWheels: Int

Also, remove the temporary init() {} you had.

The test code that you put in AppDelegate in Part 1 will no longer compile because it used the default initializer Vehicle() that no longer exists. Open AppDelegate.swift and remove it. application(_:didFinishLaunchingWithOptions:) should simply return true:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {    
  return true
}

Now you can use your new initializers to create all the different Vehicle objects. Open VehicleListViewController.swift and replace the current method body of setupVehicle() with the following:

// Clear the array. (Start from scratch.)
vehicles.removeAll(keepCapacity: true)
 
// Create a car.
var mustang = Car(brandName: "Ford", modelName: "Mustang", modelYear: 1968, powerSource: "gas engine",
  isConvertible: true, isHatchback: false, hasSunroof: false, numberOfDoors: 2)
 
// Add it to the array
vehicles.append(mustang)
 
// Create another car.
var outback = Car(brandName: "Subaru", modelName: "Outback", modelYear: 1999, powerSource: "gas engine",
  isConvertible: false, isHatchback: true, hasSunroof: false, numberOfDoors: 5)
 
// Add it to the array.
vehicles.append(outback)
 
// Create another car
var prius = Car(brandName: "Toyota", modelName: "Prius", modelYear: 2002, powerSource: "hybrid engine",
  isConvertible: false, isHatchback: true, hasSunroof: true, numberOfDoors: 4)
 
// Add it to the array.
vehicles.append(prius)
 
// Create a motorcycle
var harley = Motorcycle(brandName: "Harley-Davidson", modelName: "Softail", modelYear: 1979,
  engineNoise: "Vrrrrrrrroooooooooom!")
 
// Add it to the array.
vehicles.append(harley)
 
// Create another motorcycle
var kawasaki = Motorcycle(brandName: "Kawasaki", modelName: "Ninja", modelYear: 2005,
  engineNoise: "Neeeeeeeeeeeeeeeeow!")
 
// Add it to the array
self.vehicles.append(kawasaki)
 
// Create a truck
var silverado = Truck(brandName: "Chevrolet", modelName: "Silverado", modelYear: 2011,
  powerSource: "gas engine", numberOfWheels: 4, cargoCapacityInCubicFeet: 53)
 
// Add it to the array
vehicles.append(silverado)
 
// Create another truck
var eighteenWheeler = Truck(brandName: "Peterbilt", modelName: "579", modelYear: 2013,
  powerSource: "diesel engine", numberOfWheels: 18, cargoCapacityInCubicFeet: 408)
 
// Add it to the array
vehicles.append(eighteenWheeler)
 
// Sort the array by the model year
vehicles.sort { $0.modelYear < $1.modelYear }

Build and run the application. Everything should look as it did before, and there should be no difference in behavior.

VehicleList

Although things behave the same way, your code is now much more robust and much less susceptible errors when creating new objects. With proper initialization in the Vehicle class hierarchy, you can be sure your objects and properties start off in a known good state.

Additional Object-Oriented Patterns

There are many more Object-Oriented design patterns that you can use to improve your code. Let’s look at two more: the Adapter and the Singleton.

Both of these patterns are used extensively in iOS development, and understanding what they do under the hood will help you understand the design of the code you’ll encounter as an iOS developer.

The Adapter Pattern (Protocols)

Again from the Cocoa Fundamentals Guide:

The Adapter design pattern converts the interface of a class into another interface that clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces. It decouples the client from the class of the targeted object.

Protocols are the primary example of the Adapter pattern in Swift. This designates a number of methods and properties that can be implemented by any class. They’re most often used for DataSource and Delegate methods, but can also be used to help two unrelated classes communicate with each other.

The advantage of this pattern is that as long as a class declares that it conforms to the protocol, it really doesn’t matter whether it’s a model, a view, or a controller. It simply needs to know what is happening in the other class, and will implement any required methods or properties needed to know about this.

To make it easier to println() a Vehicle object, you will create what is called a protocol extension. This is one example of the adapter pattern in action. Open up Vehicle.swift and at the end of the file add the following stand-alone code outside of the Vehicle definition:

// MARK: An extension to make Vehicle printable
 
extension Vehicle : Printable {
  var description:String {
    return vehicleTitle + "\n" + vehicleDetails
  }
}

This declares the Vehicle class as conforming to the Printable protocol defined by the Swift Standard Library. Printable only requires that your class return a string property description. With this addition you can now println(vehicle). In other words, you have adapted Vehicle to be used as a String. To test it out, open VehicleDetailsViewController.swift and add viewWillAppear() just below viewDidLoad():

override func viewWillAppear(animated: Bool) {
  super.viewWillAppear(animated)
  if let vehicle = detailVehicle {
    println(vehicle)
  }
}

Build and run your application. Notice that whenever you tap on a vehicle, the vehicle information is printed to the Xcode console:

VehicleDebugger

Before conforming to the Printable protocol, all println() could do was print the module and class name: “Vehicle.Car”.

Bonus: Notice that Swift lets you conform to a protocol using an extension. This is called a Protocol Extension. It is a nice way to organizer your code. Here too, you are applying two patterns simultaneously again. The Adapter (via protocol conformance) and the Decorator (via extension).

The Singleton Pattern

One very specific, very useful initialization pattern is the Singleton. This ensures that a particular instance of a class is only initialized once.

This is great for items that need to only have a single instance — for instance, the UIApplication singleton sharedApplication — or for those classes that are expensive to initialize, or which store small amounts of data which need to be accessed and updated throughout your app.

In the case of your Vehicles app, you can see there’s one piece of data that might need to be accessed and updated all over the place: your list of Vehicles. The current list also violates MVC rules by letting VehicleListTableViewController manage its creation and existence. By moving the list of vehicles into its own singleton class, you gain a lot of flexibility for the future.

Go to File\New\File… and select iOS\Source\Swift File and name the file VehicleList. Open the file and create the singleton:

class VehicleList {
  let vehicles:[Vehicle] = []
 
  class var sharedInstance: VehicleList {
    struct Singleton {
      static let instance = VehicleList()
    }
    return Singleton.instance
  }
}

Swift 1.0 does not support class stored properties so the above code uses a trick to work around that limitation. First it declares a computed property called sharedInstance but instead of computing it every time, you create a local struct called Singleton that initializes the VehicleList exactly once and captures it in a static variable instance. Then you can just return Singleton.instance without it being recomputed.

Right now, you are returning an immutable empty list that is not so useful. To fix that, add an init() that creates the list.

init() {
  // Create a car.
  var mustang = Car(brandName: "Ford", modelName: "Mustang", modelYear: 1968, powerSource: "gas engine",
    isConvertible: true, isHatchback: false, hasSunroof: false, numberOfDoors: 2)
 
  // Add it to the array
  vehicles.append(mustang)
 
  // Create another car.
  var outback = Car(brandName: "Subaru", modelName: "Outback", modelYear: 1999, powerSource: "gas engine",
    isConvertible: false, isHatchback: true, hasSunroof: false, numberOfDoors: 5)
 
  // Add it to the array.
  vehicles.append(outback)
 
  // Create another car
  var prius = Car(brandName: "Toyota", modelName: "Prius", modelYear: 2002, powerSource: "hybrid engine",
    isConvertible: false, isHatchback: true, hasSunroof: true, numberOfDoors: 4)
 
  // Add it to the array.
  vehicles.append(prius)
 
  // Create a motorcycle
  var harley = Motorcycle(brandName: "Harley-Davidson", modelName: "Softail", modelYear: 1979,
    engineNoise: "Vrrrrrrrroooooooooom!")
 
  // Add it to the array.
  vehicles.append(harley)
 
  // Create another motorcycle
  var kawasaki = Motorcycle(brandName: "Kawasaki", modelName: "Ninja", modelYear: 2005,
    engineNoise: "Neeeeeeeeeeeeeeeeow!")
 
  // Add it to the array
  self.vehicles.append(kawasaki)
 
  // Create a truck
  var silverado = Truck(brandName: "Chevrolet", modelName: "Silverado", modelYear: 2011,
    powerSource: "gas engine", numberOfWheels: 4, cargoCapacityInCubicFeet: 53)
 
  // Add it to the array
  vehicles.append(silverado)
 
  // Create another truck
  var eighteenWheeler = Truck(brandName: "Peterbilt", modelName: "579", modelYear: 2013,
    powerSource: "diesel engine", numberOfWheels: 18, cargoCapacityInCubicFeet: 408)
 
  // Add it to the array
  vehicles.append(eighteenWheeler)
 
  // Sort the array by the model year
  vehicles.sort { $0.modelYear < $1.modelYear }
}

Now anywhere you reference VehicleList.sharedInstance.vehicles in your app, the list will be constructed on the first time, and simply returned on subsequent references. Under the hood, Swift uses the lib dispatch to ensure that initialization occurs exactly once, even if multiple threads access it asynchronously.

Now go back to VehicleListTableViewController.swift and remove the vehicles property as well as the entire setupVehiclesArray() method.

Modify your viewDidLoad() method by removing the call to setupVehicleArray() since you just removed the method.

You’ll notice that Xcode shows you have three errors, since there are three places where you used the vehicles property to feed the UITableViewDataSource and segue handling methods. You’ll need to update these to use your new singleton instead.

Find the three spots where Xcode indicates an error and update the code to use the VehicleList singleton’s array of vehicles instead, as shown below:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if segue.identifier == "showDetail" {
    if let indexPath = self.tableView.indexPathForSelectedRow() {
      let vehicle = VehicleList.sharedInstance.vehicles[indexPath.row]
      (segue.destinationViewController as VehicleDetailViewController).detailVehicle = vehicle
    }
  }
} 
 
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return VehicleList.sharedInstance.vehicles.count
}
 
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
 
  let vehicle = VehicleList.sharedInstance.vehicles[indexPath.row] as Vehicle
  cell.textLabel.text = vehicle.vehicleTitle
  return cell
}

Build and run your application; you’ll see the same list as you did before, but you can sleep better knowing that the code behind the app is clean, concise, and easily extensible.

With all the changes above, you’ll be able to easily add new Vehicles to this list in the future. For instance, if you were to add a new UIViewController that allows the user to add their own Vehicle, you’d only need to add it to the singleton array.

There’s one tricky thing to watch out for with singletons: they will stay alive for the entire duration of your app’s lifecycle, therefore you don’t want to load them down with too much data. They can be great for lightweight data storage or to make objects accessible throughout your application.

If you’re storing a lot of data in your app, you’ll want to look at something more robust like Core Data to handle the data storage and retrieval of your objects.

Finally, keep in mind that singletons, if overused, can lead to hard to reuse code. This is because they couple modules tightly together in the same way that global variables do. This goes to show that while patterns can be very useful in some contexts, they are not a silver bullet and can sometimes be misused.

Where To Go From Here?

In one single app, you’ve created a clean, object-oriented application using basic objects, inheritance, MVC design, polymorphism, as well as singleton and factory methods. You can review the source code of the finished project.

Apple describes how to adopt a few common Cocoa design patterns in Adopting Cocoa Design Patterns.

For more sample code, there’s a work-in-progress Design Patterns in Swift Github project trying to catalog how many common software design patterns can be implemented in Swift. Check it out and see if you can come up with better implementations yourself!

If you’ve got questions, ask away in the comments below!

Intro to Object-Oriented Design in Swift: Part 2/2 is a post from: Ray Wenderlich

The post Intro to Object-Oriented Design in Swift: Part 2/2 appeared first on Ray Wenderlich.

Viewing all 4373 articles
Browse latest View live


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