Skip to content

Instantly share code, notes, and snippets.

@pyrou
Last active October 20, 2018 16:51
Show Gist options
  • Save pyrou/e0304ea115318adc57494c5f02e18a93 to your computer and use it in GitHub Desktop.
Save pyrou/e0304ea115318adc57494c5f02e18a93 to your computer and use it in GitHub Desktop.
App Store Today tile touch effect
//
// ViewController.m
// tile
//
// Created by Michael Hurni on 20/10/2018.
// Copyright © 2018 Michael Hurni. All rights reserved.
//
#import "ViewController.h"
@interface MAHTableView : UITableView {
CGPoint initialOffset;
BOOL isPressed;
}
@end
@implementation MAHTableView
- (void)didTouchWithGestureReconizer:(UIGestureRecognizer *)g {
if([self.delegate respondsToSelector:@selector(didTouchWithGestureReconizer:)]) {
[self.delegate performSelector:@selector(didTouchWithGestureReconizer:) withObject:g];
return;
}
// default behavior, call the original didSelectRowAtIndexPath delegation method
NSIndexPath *indexPath;
if([g isKindOfClass:UILongPressGestureRecognizer.class]) {
indexPath = [self indexPathForRowAtPoint:[g locationInView:self]];
} else {
indexPath = [self indexPathForRowAtPoint:g.view.frame.origin];
}
if([self.delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) {
[self.delegate tableView:self didSelectRowAtIndexPath:indexPath];
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
// Simultaneous UIScrollViewPanGestureRecognizer and UILongPressGestureRecognizer
return ([gestureRecognizer isKindOfClass:UILongPressGestureRecognizer.class] ||
[otherGestureRecognizer isKindOfClass:UILongPressGestureRecognizer.class]);
}
- (void)addGestureReconizersToView:(UIView *)view {
UILongPressGestureRecognizer *press = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
press.minimumPressDuration = 0.2;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
tap.numberOfTapsRequired = 1;
[view addGestureRecognizer:press];
[view addGestureRecognizer:tap];
}
- (void)handleLongPressGesture:(UILongPressGestureRecognizer*)g {
if (g.state == UIGestureRecognizerStateBegan) {
if(isPressed) return;
isPressed = YES;
initialOffset = self.contentOffset;
[UIView animateWithDuration:.5 delay:0
usingSpringWithDamping:.8 initialSpringVelocity:.2
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ g.view.transform = CGAffineTransformMakeScale(0.95, 0.95); }
completion:nil];
}
else if (g.state == UIGestureRecognizerStateEnded || g.state == UIGestureRecognizerStateCancelled) {
if(isPressed == NO) return;
[self didTouchWithGestureReconizer:g];
}
if ((g.state == UIGestureRecognizerStateEnded || g.state == UIGestureRecognizerStateCancelled) ||
(g.state == UIGestureRecognizerStateChanged && fabs(self.contentOffset.y - initialOffset.y) > 20))
{
// Restore initial aspect
[UIView animateWithDuration:.5 delay:0 usingSpringWithDamping:.4 initialSpringVelocity:.2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ g.view.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { isPressed = NO; }];
}
}
- (void)handleTapGesture:(UITapGestureRecognizer *)g {
[UIView animateWithDuration:.2 delay:0
usingSpringWithDamping:.8 initialSpringVelocity:.2
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ g.view.transform = CGAffineTransformMakeScale(0.98, 0.98); }
completion:^(BOOL finished) {
[self didTouchWithGestureReconizer:g];
// Restore initial aspect
[UIView animateWithDuration:.2 delay:0 usingSpringWithDamping:.4 initialSpringVelocity:.2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ g.view.transform = CGAffineTransformIdentity; } completion:nil];
}];
}
@end
@interface ViewController () <UITableViewDelegate, UITableViewDataSource> @end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
MAHTableView *tableView = [[MAHTableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
tableView.delegate = self;
tableView.dataSource = self;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView.contentInset = UIEdgeInsetsMake(tableView.layoutMargins.top, 0, tableView.layoutMargins.bottom, 0);
[self.view addSubview:tableView];
}
- (BOOL)prefersStatusBarHidden { return YES; }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 8; }
- (CGFloat)tableView:(UITableView *)t heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return (t.bounds.size.height - t.contentInset.top) / 4.5;
}
- (UITableViewCell *)tableView:(UITableView *)t cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [t dequeueReusableCellWithIdentifier:@"cell"];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIView *tile = [UIView new];
tile.layer.cornerRadius = 10;
tile.translatesAutoresizingMaskIntoConstraints = NO;
[cell.contentView addSubview:tile];
[cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[tile]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(tile)]];
[cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[tile]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(tile)]];
[(MAHTableView *)t addGestureReconizersToView:cell];
}
cell.contentView.subviews.firstObject.backgroundColor = self.class.randomColor;
return cell;
}
- (void)tableView:(UITableView *)t didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[t cellForRowAtIndexPath:indexPath].contentView.subviews.firstObject.backgroundColor = self.class.randomColor;
}
+ (UIColor *)randomColor {
return [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
saturation:( arc4random() % 128 / 256.0 ) + .5
brightness:( arc4random() % 128 / 256.0 ) + .5 alpha:1];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment