Please note, this blog entry is from a previous course. You might want to check out the current one.
Use a UIDynamicAnimator to allow the cards in either game to be gathered up into a pile via a pinch gesture. Once gathered, the stack of gathered cards must be able to be moved around (via a pan gesture). Tapping on the stack will return the cards to their normal positions (animated, of course) unharmed.
Start by adding a pan and a pinch to the grid views of both game view controllers in storyboard and link them to actions in the generic game-view-controller class.
Add a new property to the game-view-controller class, which will store the current animations:
@property (strong, nonatomic) UIDynamicAnimator *pileAnimator;
When there is an animation active, the card should not be selectable:
- (void)touchCard:(UITapGestureRecognizer *)gesture { ... if (!card.matched && !self.pileAnimator) { ... }
… and we will reset the animation when dealing a new deck, adding new cards, and when rotating the device:
- (IBAction)touchDealButton:(UIButton *)sender { ... self.pileAnimator = nil; [self updateUI]; } - (IBAction)touchAddCardsButton:(UIButton *)sender { ... self.pileAnimator = nil; [self updateUI]; } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { ... self.pileAnimator = nil; [self updateUI]; }
After a pinch, and when there is no current active animation create a new animator, loop over all cards and add a snap behavior for each of them – snapping to the center of the pinch gesture. Because the snap behavior is quite fast, add an additional dynamic-item behavior, and set the resistance to slow snapping down:
#define RESISTANCE_TO_PILING 40.0 - (IBAction)gatherCardsIntoPile:(UIPinchGestureRecognizer *)gesture { if ((gesture.state == UIGestureRecognizerStateChanged) || (gesture.state == UIGestureRecognizerStateEnded)) { if (!self.pileAnimator) { CGPoint center = [gesture locationInView:self.gridView]; self.pileAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.gridView]; UIDynamicItemBehavior *item = [[UIDynamicItemBehavior alloc] initWithItems:self.cardViews]; item.resistance = RESISTANCE_TO_PILING; [self.pileAnimator addBehavior:item]; for (UIView *cardView in self.cardViews) { UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:cardView snapToPoint:center]; [self.pileAnimator addBehavior:snap]; } } } }
Panning should only be possible, when there is a pile (or at least the animation to generate a pile is active). When panning starts, add an attachment behavior to all cards – connected to the point of touch – and stop currently active snapping by removing the snap behavior for the cards. During panning just adjust the anchor point to the current point of touch. At the end remove the attachment behavior and reset the snapping behaviour:
- (IBAction)panPile:(UIPanGestureRecognizer *)gesture { if (self.pileAnimator) { CGPoint gesturePoint = [gesture locationInView:self.gridView]; if (gesture.state == UIGestureRecognizerStateBegan) { for (UIView *cardView in self.cardViews) { UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:cardView attachedToAnchor:gesturePoint]; [self.pileAnimator addBehavior:attachment]; } for (UIDynamicBehavior *behaviour in self.pileAnimator.behaviors) { if ([behaviour isKindOfClass:[UISnapBehavior class]]) { [self.pileAnimator removeBehavior:behaviour]; } } } else if (gesture.state == UIGestureRecognizerStateChanged) { for (UIDynamicBehavior *behaviour in self.pileAnimator.behaviors) { if ([behaviour isKindOfClass:[UIAttachmentBehavior class]]) { ((UIAttachmentBehavior *)behaviour).anchorPoint = gesturePoint; } } } else if (gesture.state == UIGestureRecognizerStateEnded) { for (UIDynamicBehavior *behaviour in self.pileAnimator.behaviors) { if ([behaviour isKindOfClass:[UIAttachmentBehavior class]]) { [self.pileAnimator removeBehavior:behaviour]; } } for (UIView *cardView in self.cardViews) { UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:cardView snapToPoint:gesturePoint]; [self.pileAnimator addBehavior:snap]; } } } }
The complete code is available on github.