How To Make a Letter / Word Game with UIKit and Swift: Part 3/3

This third and final part of the series will be the most fun of them all! In this part, you’re going to be adding a lot of cool and fun features By Caroline Begbie.

Leave a rating/review
Save for later
Share

Update note: This tutorial was updated for Swift and iOS 8 by Caroline Begbie. Original series by Tutorial Team member Marin Todorov.

Update 04/12/15: Updated for Xcode 6.3 and Swift 1.2

Welcome to the our tutorial series about creating a letter / word game with UIKit for the iPad.

If you successfully followed through the first and second parts of this tutorial, your game should be pretty functional. But it still lacks some key game elements and could use a bit more eye candy.

This third and final part of the series will be the most fun of them all! In this part, you’re going to be adding a lot of cool and fun features:

  • Visual effects to make tile dragging more satisfying
  • Gratuitous sound effects
  • In-game help for players
  • A game menu for choosing the difficulty level
  • And yes… explosions! :]

That’s quite a lot, so let’s once again get cracking!

Enhanced Tile Dragging: Not a Drag

When you run the completed project from Part 2 of this tutorial, you have this:

Counting Points

Your players can drag tiles around on the screen just fine, but the effect is very two-dimensional. In this section you’ll see how some simple visual effects can make the dragging experience much more satisfying.

When the player begins dragging a tile, it would be nice if the tile got a little bigger and showed a drop shadow below itself. These effects will make it look like the player is actually lifting the tile from the board while moving it.

To accomplish this effect, you’ll be using some features of the 2D graphics Quartz framework.

Open TileView.swift and add the following code at the end of init(letter:sideLength:), just after the line where you enabled user interactions:

//create the tile shadow
self.layer.shadowColor = UIColor.blackColor().CGColor
self.layer.shadowOpacity = 0
self.layer.shadowOffset = CGSizeMake(10.0, 10.0)
self.layer.shadowRadius = 15.0
self.layer.masksToBounds = false
    
let path = UIBezierPath(rect: self.bounds)
self.layer.shadowPath = path.CGPath

Every UIView has a property called layer – this is the CALayer instance for the lower-level drawing layer used to draw the view. Luckily CALayer has a set of properties that you can use to create a drop shadow. The property names mostly speak for themselves, but a few probably need some extra explanation.

  • masksToBounds is set to true by default, which means the layer will never render anything outside its bounds. Because a drop shadow is rendered outside of the view’s bounds, you have to set this property to false to see the shadow.
  • shadowPath is a UIBezierPath that describes the shadow shape. Use this whenever possible to speed up the shadow rendering. In the code above, you use a rectangle path (same as the tile’s bounds), but you also apply rounding to the rectangle’s corners via shadowRadius, and you effectively have a shadow with the form of the tile itself. Nice!
  • Note that you’re setting the shadowOpacity to 0, which makes the shadow invisible. So you create the shadow when the tile is initialized, but you’ll later show and hide it when the player drags the tile.

Add the following code at the end of touchesBegan(_:withEvent:):

//show the drop shadow
self.layer.shadowOpacity = 0.8

This turns on the shadow when the player starts dragging. Setting the opacity to 0.8 gives it a bit of transparency, which looks nice.

Add the following code at the end of touchesEnded(_:withEvent:):

self.layer.shadowOpacity = 0.0

This turns off the shadow. The effect will look even better when you change the tile size, but build and run the app now and drag some tiles around to see it working:

Tile with shadows

It’s a nice subtle touch that the user will appreciate – even if subconsciously.

Now to handle resizing the tile so that it looks like it’s being lifted off the board. First add the following property to the TileView class:

private var tempTransform: CGAffineTransform = CGAffineTransformIdentity

This will store the original transform for when the player stops dragging the tile. Remember, each tile has a small random rotation to begin with, so that’s the state you’ll be storing here.

Next, add this code to the end of touchesBegan(_:withEvent:):

//save the current transform
tempTransform = self.transform
//enlarge the tile
self.transform = CGAffineTransformScale(self.transform, 1.2, 1.2)

This saves the current transform and then sets the size of the tile to be 120% of the current tile size.

Now find touchesEnded(_:withEvent:) and add the following line just before the call to dragDelegate:

//restore the original transform
self.transform = tempTransform

This restores the tile’s size to its pre-drag state. While you’re at it, add the following method right after touchesEnded:

//reset the view transform in case drag is cancelled
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
  self.transform = tempTransform
  self.layer.shadowOpacity = 0.0
}

iOS calls touchesCancelled(_:withEvent:) in certain special situations, like when the app receives a low memory warning or if a notification brings up a modal alert. Your method will ensure that the tile’s display is properly restored.

Build and run and move some tiles around!

You may have already noticed this issue while developing the app, but what’s wrong with the following screenshot?

Bad Layering

As you can see, the tile the player is dragging is displayed below another tile! That really destroys the illusion of lifting the tile.

This occurs because a parent view always displays its subviews in the order that they are added to it. That means any given tile may be displayed above some tiles and below others. Not good.

To fix this, add the following code to the end of touchesBegan(_:withEvent:):

self.superview?.bringSubviewToFront(self)

This tells the view that contains the tile (self.superview) to display the tile’s view above all other views. Dragging should feel a lot better now.

Good layering

Challenge: If you want to make tile dragging even cooler, how about if touching a tile that’s underneath other tiles animated those tiles out of the way, so it looks like the pile of tiles is disturbed by lifting the bottom tile? How would you implement that?

Challenge: If you want to make tile dragging even cooler, how about if touching a tile that’s underneath other tiles animated those tiles out of the way, so it looks like the pile of tiles is disturbed by lifting the bottom tile? How would you implement that?

Contributors

Over 300 content creators. Join our team.