How To Make a Letter / Word Game with UIKit: 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 Marin Todorov.

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.h and add the following import:

#import "TileView.h"

Now make the class conform to the TileDragDelegateProtocol protocol:

@interface GameController : NSObject <TileDragDelegateProtocol>

Move on to GameController.m and add the initial implementation of the delegate method:

//a tile was dragged, check if matches a target
-(void)tileView:(TileView*)tileView didDragToPoint:(CGPoint)pt
{
    TargetView* targetView = nil;
    
    for (TargetView* tv in _targets) {
        if (CGRectContainsPoint(tv.frame, pt)) {
            targetView = tv;
            break;
        }
    }
}

This code loops over all objects in _targets and, for each of the target views, checks if 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 method 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 at the end of the same method:

//1 check if target was found
if (targetView!=nil) {

    //2 check if letter matches
    if ([targetView.letter isEqualToString: tileView.letter]) {
        
        //3
        NSLog(@"Success! You should place the tile here!");
        
        //more stuff to do on success here

        NSLog(@"Check if the player has completed the phrase");
    } else {
        //4
        NSLog(@"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. Check whether the tile was dropped on a target. If it wasn’t, targetView will still be nil
  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 user. 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 method you added to GameController is never even called because you didn’t actually set the dragDelegate property of the tiles. Doh! In GameController.m, just after [tile randomize]; in dealRandomAnagram, add the following line:

tile.dragDelegate = self;

[/spoiler]

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.m, add the following method:

-(void)placeTile:(TileView*)tileView atTarget:(TargetView*)targetView
{
    //1
    targetView.isMatched = YES;
    tileView.isMatched = YES;
      
    //2
    tileView.userInteractionEnabled = NO;
      
    //3
    [UIView animateWithDuration:0.35
                          delay:0.00
                        options:UIViewAnimationOptionCurveEaseOut
                     //4
                     animations:^{
                       tileView.center = targetView.center;
                       tileView.transform = CGAffineTransformIdentity;
                     }
                     //5 
                     completion:^(BOOL finished){
                       targetView.hidden = YES;
                     }];
}

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 user has completed the phrase.
  2. Disable user interactions for this tile. The user 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 UIViewAnimationOptionCurveEaseOut, 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 block 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.m, replace the NSLog statement that begins with “Success!” with the following:

    [self placeTile:tileView atTarget:targetView];

Now handle what happens when the player puts a tile on an incorrect target. Still inside tileView:didDragToPoint in TileView.m, replace the NSLog statement that begins with “Failure” with the following code:

    //1
    //visualize the mistake
    [tileView randomize];

    //2
    [UIView animateWithDuration:0.35
                          delay:0.00
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
                         tileView.center = CGPointMake(tileView.center.x + randomf(-20, 20),
                                                       tileView.center.y + randomf(20, 30));
                   } completion:nil];

There are only two major things happening here:

  1. You randomize the tile to demonstrate to the player that it does not match the target.
  2. You create an animation that does some extra offsetting by a random value to the tile center’s x and y positions.

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!

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

-(void)checkForSuccess
{
    for (TargetView* t in _targets) {
        //no success, bail out
        if (t.isMatched==NO) return;
    }

    NSLog(@"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.m, replace the NSLog statement that begins with “Check for” 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.