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

In this second part of the tutorial series, you’ll aim for developing a fully playable version of the game. When you’re finished, the user will be able to drag the tiles and drop them on the correct targets, where they will “stick” to the spot. By Caroline Begbie.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 6 of this article. Click here to view the first page.

Handling Tile Drops

Open up GameController.swift and add an extension to the GameController class to conform to TileDragDelegateProtocol. Place this code at the end of the file (outside the class declaration):

extension GameController:TileDragDelegateProtocol {
  //a tile was dragged, check if matches a target
  func tileView(tileView: TileView, didDragToPoint point: CGPoint) {
    var targetView: TargetView?
    for tv in targets {
      if tv.frame.contains(point) && !tv.isMatched {
        targetView = tv
        break
      }
    }
  }
}

This code declares that the GameController conforms to TileDragDelegateProtocol, and also defines tileView(tileView:didDragToPoint:) which is the initial implementation of the delegate method.

This code loops over all objects in the targets array and, for each of the target views, checks whether the given drag point is within the target’s frame.

Ha! The simple if statement effectively checks if the tile’s center point was dropped within the target – that is, whether the tile was dropped on a target. And because the point passed to this method is the tile’s center, the function will not succeed if only a small portion of the tile is within the target.

If a tile is found to be within a target, the matching target is saved to targetView.

Now go on and check whether the letter has been dragged to the correct target to make the anagram. Add the following code to the end of the same method:

//1 check if target was found
if let targetView = targetView {
      
  //2 check if letter matches
  if targetView.letter == tileView.letter {
        
    //3
      println("Success! You should place the tile here!")
        
      //more stuff to do on success here
            
      println("Check if the player has completed the phrase")
    } else {
        
    //4
    println("Failure. Let the player know this tile doesn't belong here")
        
    //more stuff to do on failure here
  }
}

Now to break this down step-by-step:

  1. If the tile was not dropped onto a target, then targetView will still be nil, and the rest of the code will not be performed
  2. Compare the tile’s letter with the target’s letter to see if they match.
  3. If the letters do match, you will do various bits of processing. For now, you just print some messages to the console.
  4. If the letters do not match, you want to indicate that to the player. Again, for now just use a log statement until you’ve verified that the logic is working correctly.

OK, build and run the project and try dragging some tiles onto targets.

If everything went well, there shouldn’t be any difference compared to what you saw last time you ran the project. But why no print statements? That’s a good question! Can you figure out why the tiles don’t react to being dropped on the targets?

[spoiler title=”Answer”]

I hope you figured it out. :] The new function you added to GameController is never even called because you didn’t actually set the dragDelegate property of the tiles. Doh![/spoiler]

In GameController.swift, just after tile.randomize() in dealRandomAnagram(), add the following line:

tile.dragDelegate = self

Run the project again and you will see your success/failure statements printed to the console when you drag a tile onto a target. Now that you know the right things are happening at the right times, you can make it do something more fun than printing to the console!

Inside GameController.swift, inside the GameController class, add the following method:

func placeTile(tileView: TileView, targetView: TargetView) {
  //1
  targetView.isMatched = true
  tileView.isMatched = true
   
  //2
  tileView.userInteractionEnabled = false
    
  //3
  UIView.animateWithDuration(0.35,
                      delay:0.00,
                    options:UIViewAnimationOptions.CurveEaseOut,
                 //4
                 animations: {
                    tileView.center = targetView.center
                    tileView.transform = CGAffineTransformIdentity
                  },
                  //5
                  completion: {
                    (value:Bool) in
                    targetView.hidden = true
                  })
}

You’ll call this method when the player successfully places a tile. Here’s what’s happening above:

  1. You set the isMatched property on both the targetView and tileView. This will help you later when you check to see if the player has completed the phrase.
  2. Disable user interactions for this tile. The player will not be able to move a tile once it’s been successfully placed.
  3. Create an animation that will last for 35 hundredths of a second. By passing in .CurveEaseOut, UIAnimation will automatically calculate an ease-out animation. Rather than making changes in even increments across the given time, it will do larger changes in the beginning and smaller changes toward the end, slowing down the animation.
  4. This closure defines the changes that should occur during the animation. In this case, you move the tile so its center is on the target’s center, and you set the tile’s transform to CGAffineTransformIdentity. The identity transform is basically no transform. This effectively undoes any changes you’ve made to scale and rotation. In this case, it straightens out the tile.
  5. When the animation is complete, you hide targetView. This isn’t absolutely necessary, since targetView will be completely behind tileView. But in general, if you know you have a view object that will not be visible, it’s more efficient to hide it so that its parent view knows it’s safe to skip it for certain types of processing.

Inside tileView(_:didDragToPoint:) in GameController.swift, replace the println() statement that begins with “Success!” with the following:

self.placeTile(tileView, targetView: targetView)

Now handle what happens when the player puts a tile on an incorrect target. Still inside tileView(_:didDragToPoint:) in GameController.swift, replace the println() statement that begins with “Failure” with the following code:

//1
tileView.randomize()
        
//2
UIView.animateWithDuration(0.35,
    delay:0.00,
    options:UIViewAnimationOptions.CurveEaseOut,
    animations: {
      tileView.center = CGPointMake(tileView.center.x + CGFloat(randomNumber(minX:0, maxX:40)-20),
                                    tileView.center.y + CGFloat(randomNumber(minX:20, maxX:30)))
    },
    completion: nil)

There are only two major things happening here:

  1. You randomize the tile’s rotation again.
  2. You create an animation that slightly offsets the tile’s position.

Taken together, these two effects demonstrate to the player that the tile does not “fit” into the dropped target.

Build and run the project. This time, instead of just statements printed to your console, you will see the tiles slide onto the target positions when dropped in the correct place, and sliding away when placed in the wrong place. Cool!

Tiles sticking

You’re ready to check if the player has finished the current game. Inside GameController.swift, add the following:

func checkForSuccess() {
  for targetView in targets {
    //no success, bail out
    if !targetView.isMatched {
      return
    }
  }
  println("Game Over!")
}

The above code loops through the targets array and checks to see if ANY TargetView is not yet matched. If it finds an unmatched target, then the game is still not over and it bails out of the method.

If all the TargetViews are matched, then your log statement “Game Over!” appears in the console. Later, you will do some more interesting things here.

Inside tileView(_:didDragToPoint:) in GameController.swift, replace the println() statement that begins with “Check if” with the following code:

//check for finished game
self.checkForSuccess()

Build and run the app and complete a puzzle. You should see your log statement printed to the console when you are finished. Great!

Contributors

Over 300 content creators. Join our team.