├── RNRippleTableView ├── en.lproj │ ├── InfoPlist.strings │ └── MainStoryboard.storyboard ├── Default.png ├── Default@2x.png ├── Default-568h@2x.png ├── RNAppDelegate.h ├── RNViewController.h ├── RNRippleTableView-Prefix.pch ├── main.m ├── RNSampleCell.h ├── RNSampleCell.m ├── RNRippleTableView-Info.plist ├── RNViewController.m └── RNAppDelegate.m ├── images ├── still.png └── animated.gif ├── RNRippleTableView.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── curiosity.xcuserdatad │ │ └── WorkspaceSettings.xcsettings ├── xcuserdata │ └── curiosity.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── RNRipplingTableView.xcscheme └── project.pbxproj ├── .gitignore ├── RNRippleTableView.podspec ├── LICENSE ├── RNRippleTableView.h ├── README.md └── RNRippleTableView.m /RNRippleTableView/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /images/still.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/RNRippleTableView/HEAD/images/still.png -------------------------------------------------------------------------------- /images/animated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/RNRippleTableView/HEAD/images/animated.gif -------------------------------------------------------------------------------- /RNRippleTableView/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/RNRippleTableView/HEAD/RNRippleTableView/Default.png -------------------------------------------------------------------------------- /RNRippleTableView/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/RNRippleTableView/HEAD/RNRippleTableView/Default@2x.png -------------------------------------------------------------------------------- /RNRippleTableView/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/RNRippleTableView/HEAD/RNRippleTableView/Default-568h@2x.png -------------------------------------------------------------------------------- /RNRippleTableView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /RNRippleTableView/RNAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNAppDelegate.h 3 | // RNRipplingTableView 4 | // 5 | // Created by Ryan Nystrom on 5/20/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RNAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RNRippleTableView/RNViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNViewController.h 3 | // RNRipplingTableView 4 | // 5 | // Created by Ryan Nystrom on 5/20/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RNRippleTableView.h" 11 | 12 | @interface RNViewController : UIViewController 13 | 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RNRippleTableView/RNRippleTableView-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'RNRipplingTableView' target in the 'RNRipplingTableView' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /RNRippleTableView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // RNRipplingTableView 4 | // 5 | // Created by Ryan Nystrom on 5/20/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "RNAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([RNAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RNRippleTableView/RNSampleCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNRippleCell.h 3 | // RNRipplingTableView 4 | // 5 | // Created by Ryan Nystrom on 5/20/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface RNSampleCell : UIView 13 | @property (nonatomic, strong, readonly) UILabel *titleLabel; 14 | @property (nonatomic, strong, readonly) CALayer *dividerLayer; 15 | @end 16 | -------------------------------------------------------------------------------- /RNRippleTableView.xcodeproj/project.xcworkspace/xcuserdata/curiosity.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /RNRippleTableView.xcodeproj/xcuserdata/curiosity.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /RNRippleTableView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "RNRippleTableView" 3 | s.version = "0.1.5" 4 | s.summary = "A custom table view with highly detailed ripple animations." 5 | s.homepage = "https://github.com/rnystrom/RNRippleTableView" 6 | s.license = 'MIT' 7 | s.author = { "Ryan Nystrom" => "rnystrom@whoisryannystrom.com" } 8 | s.source = { :git => "https://github.com/rnystrom/RNRippleTableView.git", :tag => "0.1.5" } 9 | s.source_files = 'RNRippleTableView.{h,m}' 10 | s.platform = :ios 11 | s.ios.deployment_target = '6.0' 12 | s.requires_arc = true 13 | s.frameworks = 'QuartzCore' 14 | end 15 | -------------------------------------------------------------------------------- /RNRippleTableView.xcodeproj/xcuserdata/curiosity.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RNRipplingTableView.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 86799457174A9328008D1D4C 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Ryan Nystrom (http://whoisryannystrom.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /RNRippleTableView/RNSampleCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNRippleCell.m 3 | // RNRipplingTableView 4 | // 5 | // Created by Ryan Nystrom on 5/20/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import "RNSampleCell.h" 10 | 11 | @implementation RNSampleCell 12 | 13 | - (id)initWithFrame:(CGRect)frame { 14 | if (self = [super initWithFrame:frame]) { 15 | [self _init]; 16 | } 17 | return self; 18 | } 19 | 20 | - (id)initWithCoder:(NSCoder *)aDecoder { 21 | if (self = [super initWithCoder:aDecoder]) { 22 | [self _init]; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)_init { 28 | _titleLabel = [[UILabel alloc] init]; 29 | _titleLabel.backgroundColor = [UIColor clearColor]; 30 | _titleLabel.textAlignment = NSTextAlignmentCenter; 31 | [self addSubview:_titleLabel]; 32 | 33 | _dividerLayer = [CALayer layer]; 34 | _dividerLayer.backgroundColor = [UIColor whiteColor].CGColor; 35 | [self.layer addSublayer:_dividerLayer]; 36 | } 37 | 38 | - (void)layoutSubviews { 39 | [super layoutSubviews]; 40 | self.titleLabel.frame = self.bounds; 41 | CGFloat dividerWidth = self.bounds.size.width / 2; 42 | self.dividerLayer.frame = CGRectMake(dividerWidth / 2, 0, dividerWidth, 1); 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /RNRippleTableView/RNRippleTableView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | Ryan-Nystrom.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | MainStoryboard 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UIViewEdgeAntialiasing 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /RNRippleTableView/RNViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNViewController.m 3 | // RNRipplingTableView 4 | // 5 | // Created by Ryan Nystrom on 5/20/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import "RNViewController.h" 10 | #import 11 | #import "RNSampleCell.h" 12 | 13 | @interface RNViewController () 14 | @property (weak, nonatomic) IBOutlet RNRippleTableView *rippleView; 15 | @end 16 | 17 | @implementation RNViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | self.view.backgroundColor = [UIColor colorWithRed:117/255.f green:184/255.f blue:174/255.f alpha:1]; 22 | self.rippleView.ripplesOnShake = YES; 23 | [self.rippleView registerContentViewClass:[RNSampleCell class]]; 24 | self.rippleView.delegate = self; 25 | self.rippleView.dataSource = self; 26 | } 27 | 28 | - (void)viewDidAppear:(BOOL)animated { 29 | [super viewDidAppear:animated]; 30 | [self.rippleView becomeFirstResponder]; 31 | } 32 | 33 | #pragma mark - Tableview datasource 34 | 35 | - (NSInteger)numberOfItemsInTableView:(RNRippleTableView *)tableView { 36 | return 10; 37 | } 38 | 39 | - (UIView *)viewForTableView:(RNRippleTableView *)tableView atIndex:(NSInteger)index withReuseView:(RNSampleCell *)reuseView { 40 | reuseView.backgroundColor = [UIColor colorWithRed:117/255.f green:184/255.f blue:174/255.f alpha:1]; 41 | reuseView.titleLabel.text = [NSString stringWithFormat:@"Cell %i",index+1]; 42 | reuseView.titleLabel.textColor = [UIColor whiteColor]; 43 | reuseView.titleLabel.font = [UIFont boldSystemFontOfSize:17]; 44 | reuseView.titleLabel.shadowColor = [UIColor colorWithWhite:0 alpha:0.1f]; 45 | reuseView.titleLabel.shadowOffset = CGSizeMake(0, -1); 46 | reuseView.dividerLayer.backgroundColor = [UIColor whiteColor].CGColor; 47 | return reuseView; 48 | } 49 | 50 | #pragma mark - Tableview delegate 51 | 52 | - (CGFloat)heightForViewInTableView:(RNRippleTableView *)tableView atIndex:(NSInteger)index { 53 | return 55; 54 | } 55 | 56 | - (void)tableView:(RNRippleTableView *)tableView didSelectView:(UIView *)view atIndex:(NSInteger)index { 57 | NSLog(@"Row %i tapped",index); 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /RNRippleTableView/RNAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNAppDelegate.m 3 | // RNRipplingTableView 4 | // 5 | // Created by Ryan Nystrom on 5/20/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import "RNAppDelegate.h" 10 | 11 | @implementation RNAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /RNRippleTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNTableView.h 3 | // TableView 4 | // 5 | // Created by Ryan Nystrom on 5/18/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RNRippleTableView; 12 | 13 | @protocol RNRippleTableViewDataSource 14 | @required 15 | - (NSInteger)numberOfItemsInTableView:(RNRippleTableView *)tableView; 16 | - (UIView *)viewForTableView:(RNRippleTableView *)tableView atIndex:(NSInteger)index withReuseView:(UIView *)reuseView; 17 | @end 18 | 19 | @protocol RNRippleTableViewDelegate 20 | @optional 21 | - (CGFloat)heightForViewInTableView:(RNRippleTableView *)tableView atIndex:(NSInteger)index; 22 | - (void)tableView:(RNRippleTableView *)tableView didSelectView:(UIView *)view atIndex:(NSInteger)index; 23 | @end 24 | 25 | @interface RNRippleTableView : UIScrollView 26 | 27 | @property (nonatomic, weak) id dataSource; 28 | @property (nonatomic, weak) id delegate; 29 | 30 | @property (nonatomic, assign) BOOL rippleEnabled; // default YES 31 | @property (nonatomic, assign) BOOL isAnchoredLeft; // default YES 32 | @property (nonatomic, assign) NSInteger rippleOffset; // default 3 33 | @property (nonatomic, assign) CGFloat rippleAmplitude; // default 20 (degrees) 34 | @property (nonatomic, assign) CGFloat rippleDuration; // default 0.75 seconds 35 | @property (nonatomic, assign) BOOL ripplesOnShake; // default NO 36 | @property (nonatomic, assign) CGFloat rippleDelay; // default 0.1f 37 | @property (nonatomic, assign) BOOL rippleHasParentShading; // default YES 38 | @property (nonatomic, assign) BOOL rippleHasShading; // default YES 39 | 40 | - (void)registerContentViewClass:(Class)contentViewClass; 41 | - (void)reloadData; 42 | - (NSArray *)visibleViews; 43 | - (UIView *)viewForIndex:(NSInteger)index; 44 | 45 | // animate individual cells 46 | - (void)bounceView:(UIView *)view; 47 | - (void)bounceView:(UIView *)view amplitude:(CGFloat)amplitude; 48 | - (void)bounceView:(UIView *)view amplitude:(CGFloat)amplitude duration:(CGFloat)duration; 49 | 50 | // animate ripple effect 51 | - (void)rippleAtOrigin:(NSInteger)originIndex; 52 | - (void)rippleAtOrigin:(NSInteger)originIndex amplitude:(CGFloat)amplitude; 53 | - (void)rippleAtOrigin:(NSInteger)originIndex amplitude:(CGFloat)amplitude duration:(CGFloat)duration; 54 | @end 55 | -------------------------------------------------------------------------------- /RNRippleTableView/en.lproj/MainStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /RNRippleTableView.xcodeproj/xcuserdata/curiosity.xcuserdatad/xcschemes/RNRipplingTableView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RNRippleTableView 2 | === 3 | 4 | A custom table view with highly detailed ripple animations. 5 | 6 | ## Level of Detail ## 7 | 8 | I created this control from a [couple](http://dribbble.com/shots/1072843-Filp-Menu?list=users) [posts](http://dribbble.com/shots/1072843-Filp-Menu/attachments/132747) on [Dribbble](http://dribbble.com/) by [Boris Valusek](http://dribbble.com/BorisValusek) out of pure curiosity. I really wanted to nail the shadow animations as well as a bounce effect that followed diminishing return rules on amplitude as well as plausible timing. Take a look at some screens and animations. 9 | 10 |

11 | 12 | To enable [Anti-Aliasing](http://en.wikipedia.org/wiki/Spatial_anti-aliasing) you'll need to set the [UIViewEdgeAntialiasing](http://developer.apple.com/library/ios/#documentation/general/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html) key to YES in your Info.plist. Doing so will impact performance, but if you're an optimizer you'll end up with good performance and sweet, sweet line edges like this: 13 | 14 |

15 | 16 | ## Installation ## 17 | 18 | The preferred method of installation is through [CocoaPods](http://cocoapods.org/). Just add the following to your Podfile: 19 | 20 | ``` 21 | pod 'RNRippleTableView', '~> 0.1.4' 22 | ``` 23 | 24 | Or if you want to install manually, drag both RNRippleTableView's .h and .m into your project. You'll need to ensure that the QuartzCore framework is included. 25 | 26 | Optionally, you'll need to add the following code to any view controllers using RNRippleTableView to have ripple animations on shake gestures. 27 | 28 | ```objc 29 | - (void)viewDidAppear:(BOOL)animated { 30 | [super viewDidAppear:animated]; 31 | [self.rippleView becomeFirstResponder]; 32 | } 33 | ``` 34 | 35 | ## Usage ## 36 | 37 | To use RNRippleTableView, simply add to your controller and lay it out just as you would any other view. Connect a datasource and delegate in a similar fashion to [UITableView](). You must also register the class that will be used as the table cells. This class should be a class or subclass of UIView. 38 | 39 | ```objc 40 | self.rippleView = [[RNRippleTableView alloc] init]; 41 | [self.rippleView registerContentViewClass:[RNSampleCell class]]; 42 | self.rippleView.delegate = self; 43 | self.rippleView.dataSource = self; 44 | ``` 45 | 46 | Setup your cells just like you would with [UITableView]() except without any of the identifier and checking for nil parts, that is handled for you. 47 | 48 | ```objc 49 | - (UIView *)viewForTableView:(RNRippleTableView *)tableView atIndex:(NSInteger)index withReuseView:(RNSampleCell *)reuseView { 50 | reuseView.backgroundColor = [UIColor colorWithRed:117/255.f green:184/255.f blue:174/255.f alpha:1]; 51 | reuseView.titleLabel.text = [NSString stringWithFormat:@"Cell %i",index]; 52 | reuseView.titleLabel.textColor = [UIColor whiteColor]; 53 | return reuseView; 54 | } 55 | ``` 56 | 57 | ## Customization 58 | 59 | RNRippleTableView comes with a small set of options to customize the appearance and animations. 60 | 61 | ```objc 62 | @property (nonatomic, assign) BOOL rippleEnabled; // default YES 63 | ``` 64 | 65 | Toggle rippling on and off. 66 | 67 | ```objc 68 | @property (nonatomic, assign) BOOL isAnchoredLeft; // default YES 69 | ``` 70 | 71 | Determines the anchor point for cells. Also determines which side of the view is animated as well as the direction that the shadow is drawn. 72 | 73 | ```objc 74 | @property (nonatomic, assign) NSInteger rippleOffset; // default 3 75 | ``` 76 | 77 | The number of adjacent cells that are rippled. 78 | 79 | ```objc 80 | @property (nonatomic, assign) CGFloat rippleAmplitude; // default 20 (degrees) 81 | ``` 82 | 83 | The angle at which the epicenter of the ripple animates its rotation to in degrees. 84 | 85 | ```objc 86 | @property (nonatomic, assign) CGFloat rippleDuration; // default 0.75 seconds 87 | ``` 88 | 89 | The time for a single bounce animation to complete in seconds. 90 | 91 | ```objc 92 | @property (nonatomic, assign) CGFloat rippleDelay; // default 0.1f 93 | ``` 94 | 95 | The timing delay between bounce animations in seconds that gives the entire ripple animation its delay effect. Faster looks more like a burst while slower looks like water. 96 | 97 | ```objc 98 | @property (nonatomic, assign) BOOL rippleHasParentShading; // default YES 99 | ``` 100 | 101 | Determines if the previous view casts a shadow on the animated view. 102 | 103 | ```objc 104 | @property (nonatomic, assign) BOOL rippleHasShading; // default YES 105 | ``` 106 | 107 | Determines if the entire view is shaded as it bounces. 108 | 109 | ```objc 110 | @property (nonatomic, assign) BOOL ripplesOnShake; // default NO 111 | ``` 112 | 113 | Optionally you can animate small ripples on shake gestures with the device. This was a fun feature I added just because. 114 | 115 | ## Credits 116 | 117 | I created a parent project called [RNTableView](https://github.com/rnystrom/RNTableView) simply as a bare-minimum project for starting highly customized UITableView-esque classes. That project was entirely insprired by Mike Ash's [Let's Build UITableView](http://www.mikeash.com/pyblog/friday-qa-2013-02-22-lets-build-uitableview.html) blog post. 118 | 119 | [Boris Valusek](http://dribbble.com/BorisValusek) was the creator behind this idea on [Dribbble](http://dribbble.com/). All credit for originality goes to him. 120 | 121 | ## Apps 122 | 123 | If you've used this project in a live app, please let me know! Nothing makes me happier than seeing someone else take my work and go wild with it. 124 | ## Contact 125 | 126 | * [@nystrorm](https://twitter.com/_ryannystrom) on Twitter 127 | * [@rnystrom](https://github.com/rnystrom) on Github 128 | * rnystrom [at] whoisryannystrom [dot] com 129 | 130 | ## License 131 | 132 | See [LICENSE](https://github.com/rnystrom/RNRippleTableView/blob/master/LICENSE). 133 | -------------------------------------------------------------------------------- /RNRippleTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNTableView.m 3 | // TableView 4 | // 5 | // Created by Ryan Nystrom on 5/18/13. 6 | // Copyright (c) 2013 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import "RNRippleTableView.h" 10 | #import 11 | #import 12 | 13 | #define DEGREES(rads) rads * M_PI / 180.f 14 | 15 | CGFloat const kRNRippleViewCellDefaultHeight = 40; 16 | 17 | static const void *kRNRippleTableViewParentShadowKey = &kRNRippleTableViewParentShadowKey; 18 | static const void *kRNRippleTableViewShadingLayerKey = &kRNRippleTableViewShadingLayerKey; 19 | 20 | @interface UIView (RNRippleView) 21 | @property (nonatomic, strong) CAShapeLayer *parentShadowLayer; 22 | @property (nonatomic, strong) CALayer *shadingLayer; 23 | @end 24 | 25 | @implementation UIView (RNRippleView) 26 | - (CAShapeLayer *)parentShadowLayer { 27 | return objc_getAssociatedObject(self, kRNRippleTableViewParentShadowKey); 28 | } 29 | - (void)setParentShadowLayer:(CAShapeLayer *)parentShadowLayer { 30 | objc_setAssociatedObject(self, kRNRippleTableViewParentShadowKey, parentShadowLayer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 31 | } 32 | - (CALayer *)shadingLayer { 33 | return objc_getAssociatedObject(self, kRNRippleTableViewShadingLayerKey); 34 | } 35 | - (void)setShadingLayer:(CALayer *)shadingLayer { 36 | objc_setAssociatedObject(self, kRNRippleTableViewShadingLayerKey, shadingLayer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 37 | } 38 | @end 39 | 40 | @interface RNRowObject : NSObject 41 | @property (nonatomic, strong) UIView *cachedView; 42 | @property (nonatomic, assign) NSInteger index; 43 | @property (nonatomic, assign) CGFloat startY; 44 | @property (nonatomic, assign) CGFloat height; 45 | @end 46 | @implementation RNRowObject 47 | @end 48 | 49 | @interface RNRippleTableView () 50 | @property (nonatomic, strong) NSMutableArray *reusePool; 51 | @property (nonatomic, strong) NSMutableArray *rowObjects; 52 | @property (nonatomic, copy) Class contentViewClass; 53 | @property (nonatomic, strong) NSMutableIndexSet* visibleRows; 54 | @property (nonatomic, strong) UITapGestureRecognizer *tapGesture; 55 | @end 56 | 57 | @implementation RNRippleTableView 58 | 59 | #pragma mark - UIView 60 | 61 | static void tableInit(RNRippleTableView *self) { 62 | self->_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; 63 | [self addGestureRecognizer:self->_tapGesture]; 64 | 65 | self->_reusePool = [NSMutableArray array]; 66 | self->_visibleRows = [NSMutableIndexSet indexSet]; 67 | self->_isAnchoredLeft = YES; 68 | self->_rippleAmplitude = 20; 69 | self->_rippleDuration = 0.75f; 70 | self->_rippleOffset = 3; 71 | self->_rippleDelay = 0.1f; 72 | self->_rippleEnabled = YES; 73 | self->_rippleHasParentShading = YES; 74 | self->_rippleHasShading = YES; 75 | 76 | self.bounces = YES; 77 | self.alwaysBounceHorizontal = NO; 78 | self.alwaysBounceVertical = YES; 79 | self.showsHorizontalScrollIndicator = NO; 80 | self.showsVerticalScrollIndicator = YES; 81 | } 82 | 83 | - (id)initWithFrame:(CGRect)frame { 84 | if (self = [super initWithFrame:frame]) { 85 | tableInit(self); 86 | } 87 | return self; 88 | } 89 | 90 | - (id)initWithCoder:(NSCoder *)aDecoder { 91 | if (self = [super initWithCoder:aDecoder]) { 92 | tableInit(self); 93 | } 94 | return self; 95 | } 96 | 97 | - (id)init { 98 | if (self = [super init]) { 99 | tableInit(self); 100 | } 101 | return self; 102 | } 103 | 104 | - (BOOL)canBecomeFirstResponder { 105 | return self.ripplesOnShake; 106 | } 107 | 108 | #pragma mark - Table view 109 | 110 | - (UIView *)dequeReusableView { 111 | UIView *view = [self.reusePool lastObject]; 112 | if (! view) { 113 | view = [[self.contentViewClass alloc] init]; 114 | 115 | view.shadingLayer = [CALayer layer]; 116 | view.shadingLayer.backgroundColor = [UIColor colorWithWhite:0 alpha:0.13f].CGColor; 117 | view.shadingLayer.opacity = 0; 118 | [view.layer addSublayer:view.shadingLayer]; 119 | 120 | view.parentShadowLayer = [CAShapeLayer layer]; 121 | view.parentShadowLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.5f].CGColor; 122 | view.parentShadowLayer.fillRule = kCAFillRuleNonZero; 123 | view.parentShadowLayer.opacity = 1; 124 | [view.layer addSublayer:view.parentShadowLayer]; 125 | } 126 | else { 127 | [self.reusePool removeObject:view]; 128 | } 129 | return view; 130 | } 131 | 132 | - (void)generateHeightAndOffsetData { 133 | BOOL checkForHeightEachRow = [self.delegate respondsToSelector:@selector(heightForViewInTableView:atIndex:)]; 134 | NSInteger numberOfRow = [self.dataSource numberOfItemsInTableView:self]; 135 | CGFloat startY = 0; 136 | self.rowObjects = [NSMutableArray array]; 137 | @autoreleasepool { 138 | for (NSInteger i = 0; i < numberOfRow; i++) { 139 | RNRowObject *rowObject = [[RNRowObject alloc] init]; 140 | rowObject.index = i; 141 | rowObject.height = checkForHeightEachRow ? [self.delegate heightForViewInTableView:self atIndex:i] : kRNRippleViewCellDefaultHeight; 142 | rowObject.startY = startY; 143 | startY += rowObject.height; 144 | [self.rowObjects addObject:rowObject]; 145 | } 146 | } 147 | self.contentSize = CGSizeMake(self.bounds.size.width, startY); 148 | } 149 | 150 | - (void)layoutTableView { 151 | CGFloat startY = self.contentOffset.y; 152 | CGFloat endY = startY + self.frame.size.height; 153 | NSInteger rowToDisplay = [self findRowForOffsetY:startY inRange:NSMakeRange(0, [self.rowObjects count])]; 154 | NSMutableIndexSet *newVisibleRows = [NSMutableIndexSet indexSet]; 155 | CGFloat yOrigin; 156 | CGFloat rowHeight; 157 | while (yOrigin + rowHeight < endY && rowToDisplay < [self.rowObjects count]) { 158 | [newVisibleRows addIndex:rowToDisplay]; 159 | yOrigin = [self startPositionYForIndex:rowToDisplay]; 160 | rowHeight = [self heightForIndex:rowToDisplay]; 161 | UIView *view = [self cachedViewForIndex:rowToDisplay]; 162 | if (! view) { 163 | UIView *view = [self.dataSource viewForTableView:self atIndex:rowToDisplay withReuseView:[self dequeReusableView]]; 164 | [self setCachedView:view forIndex:rowToDisplay]; 165 | view.layer.anchorPoint = CGPointMake(_isAnchoredLeft ? 0 : 1, 0.5f); 166 | view.frame = CGRectMake(0, yOrigin, self.bounds.size.width, rowHeight); 167 | view.shadingLayer.frame = view.bounds; 168 | [self insertSubview:view atIndex:0]; 169 | } 170 | rowToDisplay++; 171 | } 172 | [self returnNonVisibleRowsToThePool:newVisibleRows]; 173 | } 174 | 175 | - (NSInteger)findRowForOffsetY: (CGFloat) yPosition inRange: (NSRange) range { 176 | if ([self.rowObjects count] == 0) return 0; 177 | 178 | RNRowObject *rowObject = [[RNRowObject alloc] init]; 179 | rowObject.startY = yPosition; 180 | 181 | NSInteger index = [self.rowObjects indexOfObject:rowObject inSortedRange:NSMakeRange(0, [self.rowObjects count]) options:NSBinarySearchingInsertionIndex usingComparator:^NSComparisonResult (RNRowObject *r1, RNRowObject *r2) { 182 | if (r1.startY < r2.startY) { 183 | return NSOrderedAscending; 184 | } 185 | return NSOrderedDescending; 186 | }]; 187 | return MAX(0, index - 1); 188 | } 189 | 190 | - (void)reloadData { 191 | if (self.dataSource) { 192 | [self returnNonVisibleRowsToThePool:nil]; 193 | [self generateHeightAndOffsetData]; 194 | } 195 | } 196 | 197 | - (void)returnNonVisibleRowsToThePool:(NSMutableIndexSet*)currentVisibleRows { 198 | [self.visibleRows removeIndexes:currentVisibleRows]; 199 | [self.visibleRows enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 200 | UIView *view = [self cachedViewForIndex:idx]; 201 | if (view) { 202 | [self.reusePool addObject:view]; 203 | [view removeFromSuperview]; 204 | [self setCachedView:nil forIndex:idx]; 205 | } 206 | }]; 207 | self.visibleRows = currentVisibleRows; 208 | } 209 | 210 | #pragma mark - Setters 211 | 212 | - (void)setCachedView:(UIView *)view forIndex:(NSInteger)index { 213 | RNRowObject *rowObject = self.rowObjects[index]; 214 | rowObject.cachedView = view; 215 | } 216 | 217 | - (void)setContentOffset:(CGPoint)contentOffset { 218 | [super setContentOffset:contentOffset]; 219 | [self layoutTableView]; 220 | } 221 | 222 | - (void)registerContentViewClass:(Class)contentViewClass { 223 | NSAssert([contentViewClass isSubclassOfClass:[UIView class]], @"Cannot register a class that does not inherit from UIView."); 224 | self.contentViewClass = contentViewClass; 225 | } 226 | 227 | - (void)setDataSource:(id)dataSource { 228 | _dataSource = dataSource; 229 | [self reloadData]; 230 | } 231 | 232 | - (void)setIsAnchoredLeft:(BOOL)isAnchoredLeft { 233 | if (isAnchoredLeft != _isAnchoredLeft) { 234 | _isAnchoredLeft = isAnchoredLeft; 235 | [self reloadData]; 236 | } 237 | } 238 | 239 | #pragma mark - Getters 240 | 241 | - (UIView *)cachedViewForIndex:(NSInteger)index { 242 | return [(RNRowObject *)self.rowObjects[index] cachedView]; 243 | } 244 | 245 | - (CGFloat)heightForIndex:(NSInteger)index { 246 | return [(RNRowObject *)self.rowObjects[index] height]; 247 | } 248 | 249 | - (CGFloat)startPositionYForIndex:(NSInteger)index { 250 | return [(RNRowObject *)self.rowObjects[index] startY]; 251 | } 252 | 253 | - (NSArray *)visibleViews { 254 | if ([self.rowObjects count] > 0) { 255 | return [[self.rowObjects objectsAtIndexes:self.visibleRows] valueForKeyPath:@"cachedView"]; 256 | } 257 | return nil; 258 | } 259 | 260 | - (UIView *)viewAtPoint:(CGPoint)point { 261 | for (UIView *view in [self visibleViews]) { 262 | if (CGRectContainsPoint(view.frame, point)) return view; 263 | } 264 | return nil; 265 | } 266 | 267 | - (UIView *)viewForIndex:(NSInteger)index { 268 | return [[self.rowObjects objectAtIndex:index] cachedView]; 269 | } 270 | 271 | - (CGPathRef)parentShadowPathForView:(UIView *)view withModifier:(CGFloat)modifier { 272 | CGFloat maxHeight = 0.25f * view.bounds.size.height; 273 | UIBezierPath *path = [UIBezierPath bezierPath]; 274 | [path moveToPoint:CGPointZero]; 275 | [path addLineToPoint:CGPointMake(self.bounds.size.width, 0)]; 276 | [path addLineToPoint:CGPointMake(self.bounds.size.width * (1 - view.layer.anchorPoint.x), maxHeight * modifier)]; 277 | return CGPathRetain(path.CGPath); 278 | } 279 | 280 | #pragma mark - Gestures 281 | 282 | - (void)handleTapGesture:(UITapGestureRecognizer *)recognizer { 283 | if (recognizer == self.tapGesture && [self.delegate respondsToSelector:@selector(tableView:didSelectView:atIndex:)]) { 284 | UIView *view = [self viewAtPoint:[recognizer locationInView:self]]; 285 | if (view) { 286 | NSArray *rowObjects = [self.rowObjects filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"cachedView = %@",view]]; 287 | RNRowObject *rowObject = [rowObjects lastObject]; 288 | if (rowObject) { 289 | if (self.rippleEnabled) { 290 | [self rippleAtOrigin:rowObject.index]; 291 | } 292 | 293 | [self.delegate tableView:self didSelectView:view atIndex:rowObject.index]; 294 | } 295 | } 296 | } 297 | } 298 | 299 | - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { 300 | [super motionEnded:motion withEvent:event]; 301 | 302 | if (event.subtype == UIEventSubtypeMotionShake && self.ripplesOnShake) { 303 | [self rippleAtOrigin:[[self visibleRows] firstIndex] amplitude:5 duration:0.05f]; 304 | } 305 | } 306 | 307 | #pragma mark - Cell animation 308 | 309 | - (void)bounceView:(UIView *)view { 310 | [self bounceView:view amplitude:self.rippleAmplitude]; 311 | } 312 | 313 | - (void)bounceView:(UIView *)view amplitude:(CGFloat)amplitude { 314 | [self bounceView:view amplitude:amplitude duration:self.rippleDuration]; 315 | } 316 | 317 | - (void)bounceView:(UIView *)view amplitude:(CGFloat)amplitude duration:(CGFloat)duration { 318 | CGFloat m34 = 1 / 300.f * (view.layer.anchorPoint.x == 0 ? -1 : 1); 319 | CGFloat bounceAngleModifiers[] = {1, 0.33f, 0.13f}; 320 | NSInteger bouncesCount = sizeof(bounceAngleModifiers) / sizeof(CGFloat); 321 | bouncesCount = bouncesCount * 2 + 1; 322 | 323 | CATransform3D transform = CATransform3DIdentity; 324 | transform.m34 = m34; 325 | view.layer.transform = transform; 326 | 327 | CAKeyframeAnimation *bounceKeyframe = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.y"]; 328 | bounceKeyframe.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 329 | bounceKeyframe.duration = duration; 330 | 331 | NSMutableArray *bounceValues = [NSMutableArray array]; 332 | for (NSInteger i = 0; i < bouncesCount; i++) { 333 | CGFloat angle = 0; 334 | if (i % 2 > 0) { 335 | angle = bounceAngleModifiers[i / 2] * amplitude; 336 | } 337 | [bounceValues addObject:@(DEGREES(angle))]; 338 | } 339 | bounceKeyframe.values = bounceValues; 340 | 341 | view.parentShadowLayer.path = [self parentShadowPathForView:view withModifier:0]; 342 | [view.layer setValue:@(0) forKeyPath:bounceKeyframe.keyPath]; 343 | [view.layer addAnimation:bounceKeyframe forKey:nil]; 344 | 345 | CAKeyframeAnimation *shadowKeyframe = [bounceKeyframe copy]; 346 | shadowKeyframe.keyPath = @"opacity"; 347 | 348 | if (self.rippleHasShading) { 349 | [view.shadingLayer addAnimation:shadowKeyframe forKey:nil]; 350 | } 351 | 352 | if (self.rippleHasParentShading) { 353 | [view.parentShadowLayer addAnimation:shadowKeyframe forKey:nil]; 354 | 355 | CAKeyframeAnimation *shadowPathKeyframe = [bounceKeyframe copy]; 356 | shadowPathKeyframe.keyPath = @"path"; 357 | 358 | NSMutableArray *pathValues = [NSMutableArray array]; 359 | CGPathRef initialPath = view.parentShadowLayer.path; 360 | for (NSInteger i = 0; i < bouncesCount; i++) { 361 | CGPathRef path = initialPath; 362 | if (i % 2 > 0) { 363 | CGFloat modifier = bounceAngleModifiers[i / 2]; 364 | path = [self parentShadowPathForView:view withModifier:modifier]; 365 | } 366 | [pathValues addObject:(__bridge id)path]; 367 | } 368 | shadowPathKeyframe.values = pathValues; 369 | [view.parentShadowLayer addAnimation:shadowPathKeyframe forKey:nil]; 370 | } 371 | } 372 | 373 | #pragma mark - Table animation 374 | 375 | - (void)rippleAtOrigin:(NSInteger)originIndex { 376 | [self rippleAtOrigin:originIndex amplitude:self.rippleAmplitude]; 377 | } 378 | 379 | - (void)rippleAtOrigin:(NSInteger)originIndex amplitude:(CGFloat)amplitude { 380 | [self rippleAtOrigin:originIndex amplitude:amplitude duration:self.rippleDuration]; 381 | } 382 | 383 | - (void)rippleAtOrigin:(NSInteger)originIndex amplitude:(CGFloat)amplitude duration:(CGFloat)duration { 384 | UIView *originView = [self viewForIndex:originIndex]; 385 | 386 | [self bounceView:originView amplitude:amplitude]; 387 | 388 | CGFloat delay = self.rippleDelay; 389 | NSMutableArray *viewGroups = [NSMutableArray array]; 390 | NSArray *visibleViews = [self visibleViews]; 391 | 392 | for (NSInteger i = 1; i <= self.rippleOffset; i++) { 393 | NSMutableArray *viewGroup = [NSMutableArray array]; 394 | if (originIndex - i > -1) { 395 | UIView *view = [self viewForIndex:originIndex - i]; 396 | if (view && [visibleViews containsObject:view]) { 397 | [viewGroup addObject:view]; 398 | } 399 | } 400 | if (originIndex + i < [self.dataSource numberOfItemsInTableView:self]) { 401 | UIView *view = [self viewForIndex:originIndex + i]; 402 | if (view && [visibleViews containsObject:view]) { 403 | [viewGroup addObject:view]; 404 | } 405 | } 406 | if ([viewGroup count] > 0) { 407 | [viewGroups addObject:viewGroup]; 408 | } 409 | } 410 | 411 | [viewGroups enumerateObjectsUsingBlock:^(NSArray *viewGroup, NSUInteger idx, BOOL *stop) { 412 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * (idx + 1) * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 413 | CGFloat modifier = 1 / (1.f * idx + 1); 414 | modifier = powf(modifier, idx); 415 | CGFloat subAmplitude = amplitude * modifier; 416 | for (UIView *view in viewGroup) { 417 | [self bounceView:view amplitude:subAmplitude]; 418 | } 419 | }); 420 | }]; 421 | } 422 | 423 | @end 424 | -------------------------------------------------------------------------------- /RNRippleTableView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 863838BD174E7EC2007DB88D /* RNAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 863838B6174E7EC2007DB88D /* RNAppDelegate.m */; }; 11 | 863838BF174E7EC2007DB88D /* RNSampleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 863838BA174E7EC2007DB88D /* RNSampleCell.m */; }; 12 | 863838C0174E7EC2007DB88D /* RNViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 863838BC174E7EC2007DB88D /* RNViewController.m */; }; 13 | 863838C5174E7ED9007DB88D /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 863838C1174E7ED9007DB88D /* Default-568h@2x.png */; }; 14 | 863838C6174E7ED9007DB88D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 863838C2174E7ED9007DB88D /* Default.png */; }; 15 | 863838C7174E7ED9007DB88D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 863838C3174E7ED9007DB88D /* Default@2x.png */; }; 16 | 863838C8174E7ED9007DB88D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 863838C4174E7ED9007DB88D /* main.m */; }; 17 | 863838CD174E7EE0007DB88D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 863838C9174E7EE0007DB88D /* InfoPlist.strings */; }; 18 | 863838CE174E7EE0007DB88D /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 863838CB174E7EE0007DB88D /* MainStoryboard.storyboard */; }; 19 | 8679945C174A9328008D1D4C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8679945B174A9328008D1D4C /* UIKit.framework */; }; 20 | 8679945E174A9328008D1D4C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8679945D174A9328008D1D4C /* Foundation.framework */; }; 21 | 86799460174A9328008D1D4C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8679945F174A9328008D1D4C /* CoreGraphics.framework */; }; 22 | 86799480174A9346008D1D4C /* RNRippleTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8679947F174A9346008D1D4C /* RNRippleTableView.m */; }; 23 | 86799485174A947C008D1D4C /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86799484174A947C008D1D4C /* QuartzCore.framework */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 863838B5174E7EC2007DB88D /* RNAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNAppDelegate.h; path = RNRippleTableView/RNAppDelegate.h; sourceTree = SOURCE_ROOT; }; 28 | 863838B6174E7EC2007DB88D /* RNAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNAppDelegate.m; path = RNRippleTableView/RNAppDelegate.m; sourceTree = SOURCE_ROOT; }; 29 | 863838B7174E7EC2007DB88D /* RNRippleTableView-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "RNRippleTableView-Info.plist"; path = "RNRippleTableView/RNRippleTableView-Info.plist"; sourceTree = SOURCE_ROOT; }; 30 | 863838B8174E7EC2007DB88D /* RNRippleTableView-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNRippleTableView-Prefix.pch"; path = "RNRippleTableView/RNRippleTableView-Prefix.pch"; sourceTree = SOURCE_ROOT; }; 31 | 863838B9174E7EC2007DB88D /* RNSampleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSampleCell.h; path = RNRippleTableView/RNSampleCell.h; sourceTree = SOURCE_ROOT; }; 32 | 863838BA174E7EC2007DB88D /* RNSampleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSampleCell.m; path = RNRippleTableView/RNSampleCell.m; sourceTree = SOURCE_ROOT; }; 33 | 863838BB174E7EC2007DB88D /* RNViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNViewController.h; path = RNRippleTableView/RNViewController.h; sourceTree = SOURCE_ROOT; }; 34 | 863838BC174E7EC2007DB88D /* RNViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNViewController.m; path = RNRippleTableView/RNViewController.m; sourceTree = SOURCE_ROOT; }; 35 | 863838C1174E7ED9007DB88D /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "RNRippleTableView/Default-568h@2x.png"; sourceTree = SOURCE_ROOT; }; 36 | 863838C2174E7ED9007DB88D /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = RNRippleTableView/Default.png; sourceTree = SOURCE_ROOT; }; 37 | 863838C3174E7ED9007DB88D /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default@2x.png"; path = "RNRippleTableView/Default@2x.png"; sourceTree = SOURCE_ROOT; }; 38 | 863838C4174E7ED9007DB88D /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RNRippleTableView/main.m; sourceTree = SOURCE_ROOT; }; 39 | 863838CA174E7EE0007DB88D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = RNRippleTableView/en.lproj/InfoPlist.strings; sourceTree = SOURCE_ROOT; }; 40 | 863838CC174E7EE0007DB88D /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = RNRippleTableView/en.lproj/MainStoryboard.storyboard; sourceTree = SOURCE_ROOT; }; 41 | 86799458174A9328008D1D4C /* RNRippleTableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RNRippleTableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 8679945B174A9328008D1D4C /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 43 | 8679945D174A9328008D1D4C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 44 | 8679945F174A9328008D1D4C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 45 | 8679947E174A9346008D1D4C /* RNRippleTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNRippleTableView.h; sourceTree = ""; }; 46 | 8679947F174A9346008D1D4C /* RNRippleTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNRippleTableView.m; sourceTree = ""; }; 47 | 86799484174A947C008D1D4C /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 86799455174A9328008D1D4C /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | 86799485174A947C008D1D4C /* QuartzCore.framework in Frameworks */, 56 | 8679945C174A9328008D1D4C /* UIKit.framework in Frameworks */, 57 | 8679945E174A9328008D1D4C /* Foundation.framework in Frameworks */, 58 | 86799460174A9328008D1D4C /* CoreGraphics.framework in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | 8679944F174A9328008D1D4C = { 66 | isa = PBXGroup; 67 | children = ( 68 | 8679947E174A9346008D1D4C /* RNRippleTableView.h */, 69 | 8679947F174A9346008D1D4C /* RNRippleTableView.m */, 70 | 86799461174A9328008D1D4C /* RNRippleTableView */, 71 | 8679945A174A9328008D1D4C /* Frameworks */, 72 | 86799459174A9328008D1D4C /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 86799459174A9328008D1D4C /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 86799458174A9328008D1D4C /* RNRippleTableView.app */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 8679945A174A9328008D1D4C /* Frameworks */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 86799484174A947C008D1D4C /* QuartzCore.framework */, 88 | 8679945B174A9328008D1D4C /* UIKit.framework */, 89 | 8679945D174A9328008D1D4C /* Foundation.framework */, 90 | 8679945F174A9328008D1D4C /* CoreGraphics.framework */, 91 | ); 92 | name = Frameworks; 93 | sourceTree = ""; 94 | }; 95 | 86799461174A9328008D1D4C /* RNRippleTableView */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 863838B9174E7EC2007DB88D /* RNSampleCell.h */, 99 | 863838BA174E7EC2007DB88D /* RNSampleCell.m */, 100 | 863838BB174E7EC2007DB88D /* RNViewController.h */, 101 | 863838BC174E7EC2007DB88D /* RNViewController.m */, 102 | 86799462174A9328008D1D4C /* Supporting Files */, 103 | ); 104 | name = RNRippleTableView; 105 | path = RNRipplingTableView; 106 | sourceTree = ""; 107 | }; 108 | 86799462174A9328008D1D4C /* Supporting Files */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 863838B5174E7EC2007DB88D /* RNAppDelegate.h */, 112 | 863838B6174E7EC2007DB88D /* RNAppDelegate.m */, 113 | 863838B7174E7EC2007DB88D /* RNRippleTableView-Info.plist */, 114 | 863838B8174E7EC2007DB88D /* RNRippleTableView-Prefix.pch */, 115 | 863838C9174E7EE0007DB88D /* InfoPlist.strings */, 116 | 863838CB174E7EE0007DB88D /* MainStoryboard.storyboard */, 117 | 863838C1174E7ED9007DB88D /* Default-568h@2x.png */, 118 | 863838C2174E7ED9007DB88D /* Default.png */, 119 | 863838C3174E7ED9007DB88D /* Default@2x.png */, 120 | 863838C4174E7ED9007DB88D /* main.m */, 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | /* End PBXGroup section */ 126 | 127 | /* Begin PBXNativeTarget section */ 128 | 86799457174A9328008D1D4C /* RNRippleTableView */ = { 129 | isa = PBXNativeTarget; 130 | buildConfigurationList = 8679947B174A9328008D1D4C /* Build configuration list for PBXNativeTarget "RNRippleTableView" */; 131 | buildPhases = ( 132 | 86799454174A9328008D1D4C /* Sources */, 133 | 86799455174A9328008D1D4C /* Frameworks */, 134 | 86799456174A9328008D1D4C /* Resources */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = RNRippleTableView; 141 | productName = RNRipplingTableView; 142 | productReference = 86799458174A9328008D1D4C /* RNRippleTableView.app */; 143 | productType = "com.apple.product-type.application"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | 86799450174A9328008D1D4C /* Project object */ = { 149 | isa = PBXProject; 150 | attributes = { 151 | CLASSPREFIX = RN; 152 | LastUpgradeCheck = 0460; 153 | ORGANIZATIONNAME = "Ryan Nystrom"; 154 | }; 155 | buildConfigurationList = 86799453174A9328008D1D4C /* Build configuration list for PBXProject "RNRippleTableView" */; 156 | compatibilityVersion = "Xcode 3.2"; 157 | developmentRegion = English; 158 | hasScannedForEncodings = 0; 159 | knownRegions = ( 160 | en, 161 | ); 162 | mainGroup = 8679944F174A9328008D1D4C; 163 | productRefGroup = 86799459174A9328008D1D4C /* Products */; 164 | projectDirPath = ""; 165 | projectRoot = ""; 166 | targets = ( 167 | 86799457174A9328008D1D4C /* RNRippleTableView */, 168 | ); 169 | }; 170 | /* End PBXProject section */ 171 | 172 | /* Begin PBXResourcesBuildPhase section */ 173 | 86799456174A9328008D1D4C /* Resources */ = { 174 | isa = PBXResourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | 863838C5174E7ED9007DB88D /* Default-568h@2x.png in Resources */, 178 | 863838C6174E7ED9007DB88D /* Default.png in Resources */, 179 | 863838C7174E7ED9007DB88D /* Default@2x.png in Resources */, 180 | 863838CD174E7EE0007DB88D /* InfoPlist.strings in Resources */, 181 | 863838CE174E7EE0007DB88D /* MainStoryboard.storyboard in Resources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXResourcesBuildPhase section */ 186 | 187 | /* Begin PBXSourcesBuildPhase section */ 188 | 86799454174A9328008D1D4C /* Sources */ = { 189 | isa = PBXSourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 86799480174A9346008D1D4C /* RNRippleTableView.m in Sources */, 193 | 863838BD174E7EC2007DB88D /* RNAppDelegate.m in Sources */, 194 | 863838BF174E7EC2007DB88D /* RNSampleCell.m in Sources */, 195 | 863838C0174E7EC2007DB88D /* RNViewController.m in Sources */, 196 | 863838C8174E7ED9007DB88D /* main.m in Sources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXSourcesBuildPhase section */ 201 | 202 | /* Begin PBXVariantGroup section */ 203 | 863838C9174E7EE0007DB88D /* InfoPlist.strings */ = { 204 | isa = PBXVariantGroup; 205 | children = ( 206 | 863838CA174E7EE0007DB88D /* en */, 207 | ); 208 | name = InfoPlist.strings; 209 | sourceTree = ""; 210 | }; 211 | 863838CB174E7EE0007DB88D /* MainStoryboard.storyboard */ = { 212 | isa = PBXVariantGroup; 213 | children = ( 214 | 863838CC174E7EE0007DB88D /* en */, 215 | ); 216 | name = MainStoryboard.storyboard; 217 | sourceTree = ""; 218 | }; 219 | /* End PBXVariantGroup section */ 220 | 221 | /* Begin XCBuildConfiguration section */ 222 | 86799479174A9328008D1D4C /* Debug */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_OBJC_ARC = YES; 229 | CLANG_WARN_CONSTANT_CONVERSION = YES; 230 | CLANG_WARN_EMPTY_BODY = YES; 231 | CLANG_WARN_ENUM_CONVERSION = YES; 232 | CLANG_WARN_INT_CONVERSION = YES; 233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 234 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 235 | COPY_PHASE_STRIP = NO; 236 | GCC_C_LANGUAGE_STANDARD = gnu99; 237 | GCC_DYNAMIC_NO_PIC = NO; 238 | GCC_OPTIMIZATION_LEVEL = 0; 239 | GCC_PREPROCESSOR_DEFINITIONS = ( 240 | "DEBUG=1", 241 | "$(inherited)", 242 | ); 243 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 244 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 245 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 246 | GCC_WARN_UNUSED_VARIABLE = YES; 247 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 248 | ONLY_ACTIVE_ARCH = YES; 249 | SDKROOT = iphoneos; 250 | }; 251 | name = Debug; 252 | }; 253 | 8679947A174A9328008D1D4C /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_OBJC_ARC = YES; 260 | CLANG_WARN_CONSTANT_CONVERSION = YES; 261 | CLANG_WARN_EMPTY_BODY = YES; 262 | CLANG_WARN_ENUM_CONVERSION = YES; 263 | CLANG_WARN_INT_CONVERSION = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 266 | COPY_PHASE_STRIP = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 269 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 272 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 273 | SDKROOT = iphoneos; 274 | VALIDATE_PRODUCT = YES; 275 | }; 276 | name = Release; 277 | }; 278 | 8679947C174A9328008D1D4C /* Debug */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 282 | GCC_PREFIX_HEADER = "RNRippleTableView/RNRippleTableView-Prefix.pch"; 283 | INFOPLIST_FILE = "RNRippleTableView/RNRippleTableView-Info.plist"; 284 | PRODUCT_NAME = RNRippleTableView; 285 | WRAPPER_EXTENSION = app; 286 | }; 287 | name = Debug; 288 | }; 289 | 8679947D174A9328008D1D4C /* Release */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 293 | GCC_PREFIX_HEADER = "RNRippleTableView/RNRippleTableView-Prefix.pch"; 294 | INFOPLIST_FILE = "RNRippleTableView/RNRippleTableView-Info.plist"; 295 | PRODUCT_NAME = RNRippleTableView; 296 | WRAPPER_EXTENSION = app; 297 | }; 298 | name = Release; 299 | }; 300 | /* End XCBuildConfiguration section */ 301 | 302 | /* Begin XCConfigurationList section */ 303 | 86799453174A9328008D1D4C /* Build configuration list for PBXProject "RNRippleTableView" */ = { 304 | isa = XCConfigurationList; 305 | buildConfigurations = ( 306 | 86799479174A9328008D1D4C /* Debug */, 307 | 8679947A174A9328008D1D4C /* Release */, 308 | ); 309 | defaultConfigurationIsVisible = 0; 310 | defaultConfigurationName = Release; 311 | }; 312 | 8679947B174A9328008D1D4C /* Build configuration list for PBXNativeTarget "RNRippleTableView" */ = { 313 | isa = XCConfigurationList; 314 | buildConfigurations = ( 315 | 8679947C174A9328008D1D4C /* Debug */, 316 | 8679947D174A9328008D1D4C /* Release */, 317 | ); 318 | defaultConfigurationIsVisible = 0; 319 | defaultConfigurationName = Release; 320 | }; 321 | /* End XCConfigurationList section */ 322 | }; 323 | rootObject = 86799450174A9328008D1D4C /* Project object */; 324 | } 325 | --------------------------------------------------------------------------------