cs193p – Assignment #4 Task #6

Please note, this blog entry is from a previous course. You might want to check out the current one.

The arrival and departure of cards must be animated and, as they come and go, you must automatically adjust the layout of the cards in your user-interface (i.e. their size and position) to efficiently use the real estate on screen (i.e. don’t waste space) and make them all fit.

… a minor bug was introduced in task #3 … the code was added to the super class, thus also playing cards get removed which makes the game really difficult. Lets add a new public property to choose if card should be removed or stay:

@property (nonatomic) BOOL removeMatchingCards;


Which is not set by default, and needs to be set for the set game:

- (void)viewDidLoad
{
    ...
    self.removeMatchingCards = YES;
}

When updating the user interface, either remove the cards of “disable” them:

- (void)updateUI
{
            ...
                if (self.removeMatchingCards) {
                    ...
                } else {
                    cardView.alpha = card.matched ? 0.6 : 1.0;
                }
            ...
}

Now that the matching playing cards stay available, it’s necessary to take care that the tap gesture does not trigger an other flip for those cards:

- (void)touchCard:(UITapGestureRecognizer *)gesture
{
        ...
        if (!card.matched) {
            ...
        }
    ...
}

… now to the current task:

When a card changes its frame, because it is new, or the cards need to be resized or repositioned, start a delayed animation of that card. The delay increases for each card for a “nicer” effect. Unfortunately rounding errors made it impossible to use the built-in compare function for CGRects, thus we need to use a small helper method as work around:

- (void)updateUI
{
    ...
    self.grid.minimumNumberOfCells = [self.cardViews count];    
    NSUInteger changedViews = 0;
    for (NSUInteger viewIndex = 0; viewIndex < [self.cardViews count]; viewIndex++) {
        CGRect frame = [self.grid frameOfCellAtRow:viewIndex / self.grid.columnCount
                                          inColumn:viewIndex % self.grid.columnCount];
        frame = CGRectInset(frame, frame.size.width * CARDSPACINGINPERCENT, frame.size.height * CARDSPACINGINPERCENT);
        UIView *cardView = (UIView *)self.cardViews[viewIndex];
        if (![self frame:frame equalToFrame:cardView.frame]) {
            [UIView animateWithDuration:0.5
                                  delay:1.5 * changedViews++ / [self.cardViews count]
                                options:UIViewAnimationOptionCurveEaseInOut
                             animations:^{
                                 cardView.frame = frame;
                             } completion:NULL];
        }
    }
    ...
}

#define FRAMEROUNDINGERROR 0.01
- (BOOL)frame:(CGRect)frame1 equalToFrame:(CGRect)frame2
{
    if (fabs(frame1.size.width - frame2.size.width) > FRAMEROUNDINGERROR) return NO;
    if (fabs(frame1.size.height - frame2.size.height) > FRAMEROUNDINGERROR) return NO;
    if (fabs(frame1.origin.x - frame2.origin.x) > FRAMEROUNDINGERROR) return NO;
    if (fabs(frame1.origin.y - frame2.origin.y) > FRAMEROUNDINGERROR) return NO;
    return YES;
}

The current code would animate new cards from the top left corner. To change that to the bottom right corner set the initial frame when creating the view:

- (void)updateUI
{
                ...
                cardView.frame = CGRectMake(self.gridView.bounds.size.width,
                                            self.gridView.bounds.size.height,
                                            self.grid.cellSize.width,
                                            self.grid.cellSize.height);
                ...
}

The removal of cards gets an animation of its own, sending the cards to the bottom left corner, before removing them completely:

- (void)updateUI
{
                ...
                    [self.cardViews removeObject:cardView];
                    [UIView animateWithDuration:1.0
                                     animations:^{
                                         cardView.frame = CGRectMake(0.0,
                                                                     self.gridView.bounds.size.height,
                                                                     self.grid.cellSize.width,
                                                                     self.grid.cellSize.height);
                                         
                                     } completion:^(BOOL finished) {
                                         [cardView removeFromSuperview];
                                     }];                    
                ...
}

The complete code is available on github.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

2 thoughts on “cs193p – Assignment #4 Task #6”

  1. I have a problem with line 202 of your code on GitHub. I get the following error:

    No visible @interface for ‘CardGameViewController’ declares the selector ‘frame:equalToFrame:’

    1. Please have a look at the fifth code block of the post, there the method you are missing is defined. In the github code, it should be on line 218. Which Xcode Version are you using?

Leave a Reply

Your email address will not be published.