├── Sample ├── Sample │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Default.png │ ├── Default@2x.png │ ├── Default-568h@2x.png │ ├── RSViewController.h │ ├── RSSimpleCardView.h │ ├── RSSimpleCard.h │ ├── Sample-Prefix.pch │ ├── main.m │ ├── RSAppDelegate.h │ ├── RSSimpleCard.m │ ├── RSSimpleCardView.m │ ├── Sample-Info.plist │ ├── RSAppDelegate.m │ └── RSViewController.m └── Sample.xcodeproj │ ├── xcuserdata │ └── R0CKSTAR.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── Sample.xcscheme │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── R0CKSTAR.xcuserdatad │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── Sample.xccheckout │ └── project.pbxproj ├── Card View ├── Settings@2x.png ├── RSCard.h ├── RSCard.m ├── RSCardView.h ├── RSCardsView.h ├── UIView+Folding.h ├── UIColor+Expanded.h ├── RSCardView.m ├── UIColor+Expanded.m ├── UIView+Folding.m └── RSCardsView.m ├── MIT-LICENSE.txt └── README.md /Sample/Sample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Card View/Settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeahdongcn/RSGoogleNowStyleCardsView/HEAD/Card View/Settings@2x.png -------------------------------------------------------------------------------- /Sample/Sample/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeahdongcn/RSGoogleNowStyleCardsView/HEAD/Sample/Sample/Default.png -------------------------------------------------------------------------------- /Sample/Sample/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeahdongcn/RSGoogleNowStyleCardsView/HEAD/Sample/Sample/Default@2x.png -------------------------------------------------------------------------------- /Sample/Sample/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeahdongcn/RSGoogleNowStyleCardsView/HEAD/Sample/Sample/Default-568h@2x.png -------------------------------------------------------------------------------- /Sample/Sample.xcodeproj/xcuserdata/R0CKSTAR.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Sample/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/Sample/RSViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSViewController.h 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RSViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Sample/Sample/RSSimpleCardView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSSimpleCardView.h 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSCardView.h" 10 | 11 | @interface RSSimpleCardView : RSCardView 12 | 13 | - (void)setText:(NSString *)text; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Sample/Sample/RSSimpleCard.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSSimpleCard.h 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSCard.h" 10 | 11 | @interface RSSimpleCard : RSCard 12 | 13 | - (id)initWithText:(NSString *)text; 14 | 15 | - (NSString *)text; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Sample/Sample/Sample-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Sample' target in the 'Sample' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Sample/Sample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "RSAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([RSAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sample/Sample.xcodeproj/project.xcworkspace/xcuserdata/R0CKSTAR.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Sample/Sample/RSAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSAppDelegate.h 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RSViewController; 12 | 13 | @interface RSAppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @property (strong, nonatomic) RSViewController *viewController; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Card View/RSCard.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSCard.h 3 | // Google Now Style Card View 4 | // 5 | // Created by R0CKSTAR on 5/21/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RSCard : NSObject 12 | 13 | // Override these functions to customize 14 | 15 | + (BOOL)isUserLocationRequired; 16 | 17 | + (BOOL)shouldInsertInSection; 18 | 19 | + (CGFloat)contentHeight; 20 | 21 | + (CGFloat)settingsHeight; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Card View/RSCard.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSCard.m 3 | // Google Now Style Card View 4 | // 5 | // Created by R0CKSTAR on 5/21/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSCard.h" 10 | 11 | @implementation RSCard 12 | 13 | + (BOOL)isUserLocationRequired { 14 | return NO; 15 | } 16 | 17 | + (BOOL)shouldInsertInSection { 18 | return YES; 19 | } 20 | 21 | + (CGFloat)contentHeight { 22 | return 0; 23 | } 24 | 25 | + (CGFloat)settingsHeight { 26 | return 0; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Sample/Sample.xcodeproj/xcuserdata/R0CKSTAR.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Sample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | D0F2B1A51780511E002053BA 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sample/Sample/RSSimpleCard.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSSimpleCard.m 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSSimpleCard.h" 10 | 11 | @interface RSSimpleCard () { 12 | NSString *_text; 13 | } 14 | 15 | @end 16 | 17 | @implementation RSSimpleCard 18 | 19 | - (id)initWithText:(NSString *)text { 20 | self = [super init]; 21 | 22 | if (self) { 23 | _text = [text retain]; 24 | } 25 | 26 | return self; 27 | } 28 | 29 | - (void)dealloc { 30 | [_text release]; 31 | [super dealloc]; 32 | } 33 | 34 | - (NSString *)text { 35 | return _text; 36 | } 37 | 38 | + (CGFloat)contentHeight { 39 | return 100; 40 | } 41 | 42 | + (CGFloat)settingsHeight { 43 | return 100; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Sample/Sample/RSSimpleCardView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSSimpleCardView.m 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSSimpleCardView.h" 10 | 11 | @interface RSSimpleCardView () 12 | { 13 | UILabel *_textLabel; 14 | } 15 | 16 | @end 17 | 18 | @implementation RSSimpleCardView 19 | 20 | - (void)setText:(NSString *)text { 21 | if (!_textLabel) { 22 | _textLabel = [[[UILabel alloc] initWithFrame:CGRectMake(5, 5, _contentView.bounds.size.width - 5 * 2, _contentView.bounds.size.height - 5 * 2)] autorelease]; 23 | _textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 24 | _textLabel.backgroundColor = [UIColor clearColor]; 25 | _textLabel.textColor = [UIColor blackColor]; 26 | _textLabel.numberOfLines = 4; 27 | _textLabel.font = [UIFont systemFontOfSize:16]; 28 | [_contentView addSubview:_textLabel]; 29 | } 30 | 31 | _textLabel.text = text; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 P.D.Q. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Google-Now-Style-Card-View 2 | ========================== 3 | 4 | ![ScreenShot](https://s3.amazonaws.com/cocoacontrols_production/uploads/control_image/image/1784/iOS_Simulator_Screen_shot_Sep_5__2013_3.44.58_PM.png) 5 | 6 | Google Search for Android has been updated to take advantage of various new Google Now cards, including one for live TV. 7 | 8 | This project clones the card inserting animation, card exchange animation and provides UITableView alike APIs for data sourcing and delegating. 9 | 10 | Add 'Card View' (Dir) to your project and start to use RSCardsView. 11 | 12 | RSCardsView *view = [[[RSCardsView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame] autorelease]; 13 | view.delegate = self; 14 | view.dataSource = self; 15 | view.animationStyle = RSCardsViewAnimationStyleExchange; // or RSCardsViewAnimationStyleDrop 16 | self.view = view; 17 | 18 | You should implement your own card and card view, open sample to see lot more. 19 | 20 | [![ScreenShot](https://raw.github.com/GabLeRoux/WebMole/master/ressources/WebMole_Youtube_Video.png)](http://v.youku.com/v_show/id_XNTc4MDUyODY0.html) 21 | 22 | 23 | 24 | 25 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/yeahdongcn/rsgooglenowstylecardsview/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 26 | 27 | -------------------------------------------------------------------------------- /Sample/Sample/Sample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.pdq.${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 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Card View/RSCardView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSCardView.h 3 | // Google Now Style Card View 4 | // 5 | // Created by R0CKSTAR on 5/21/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // Swip direction enum 12 | typedef NS_ENUM (NSUInteger, RSCardViewSwipeDirection) { 13 | RSCardViewSwipeDirectionLeft = 0, 14 | RSCardViewSwipeDirectionCenter, 15 | RSCardViewSwipeDirectionRight 16 | }; 17 | 18 | @class RSCardView; 19 | 20 | @protocol RSCardViewDelegate 21 | 22 | @required 23 | 24 | - (BOOL)canToggleSettings:(RSCardView *)cardView; 25 | 26 | - (void)didRemoveFromSuperview:(RSCardView *)cardView; 27 | 28 | - (void)didTapOnCard:(RSCardView *)cardView; 29 | 30 | - (void)didChangeFrame:(RSCardView *)cardView; 31 | 32 | - (UIColor *)superviewBackgroundColor; 33 | 34 | - (void)insertAnimationDidStart; 35 | 36 | - (void)insertAnimationDidStop; 37 | 38 | @end 39 | 40 | @interface RSCardView : UIView { 41 | UIView *_contentView; 42 | UIButton *_settingsButton; 43 | UIView *_settingsView; 44 | } 45 | 46 | @property (assign, nonatomic) id delegate; 47 | 48 | @property (assign, nonatomic) BOOL shouldOpenSettingsLater; 49 | 50 | - (void)setContentViewHeight:(CGFloat)height animated:(BOOL)animated; 51 | 52 | - (void)setSettingsViewHeight:(CGFloat)height; 53 | 54 | - (BOOL)isSettingsVisible; 55 | 56 | - (void)toggleSettings; 57 | 58 | - (void)insertAnimation; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Sample/Sample.xcodeproj/project.xcworkspace/xcshareddata/Sample.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectIdentifier 6 | 0929F2C1-301B-4DA9-BE59-36D0A8006912 7 | IDESourceControlProjectName 8 | Sample 9 | IDESourceControlProjectOriginsDictionary 10 | 11 | E0A8E8FE-FAA7-4E6F-86FF-1DD6F73FD9C3 12 | https://github.com/yeahdongcn/Google-Now-Style-Card-View.git 13 | 14 | IDESourceControlProjectPath 15 | Sample/Sample.xcodeproj/project.xcworkspace 16 | IDESourceControlProjectRelativeInstallPathDictionary 17 | 18 | E0A8E8FE-FAA7-4E6F-86FF-1DD6F73FD9C3 19 | ../../.. 20 | 21 | IDESourceControlProjectURL 22 | https://github.com/yeahdongcn/Google-Now-Style-Card-View.git 23 | IDESourceControlProjectVersion 24 | 110 25 | IDESourceControlProjectWCCIdentifier 26 | E0A8E8FE-FAA7-4E6F-86FF-1DD6F73FD9C3 27 | IDESourceControlProjectWCConfigurations 28 | 29 | 30 | IDESourceControlRepositoryExtensionIdentifierKey 31 | public.vcs.git 32 | IDESourceControlWCCIdentifierKey 33 | E0A8E8FE-FAA7-4E6F-86FF-1DD6F73FD9C3 34 | IDESourceControlWCCName 35 | Google Now Style Card View 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Card View/RSCardsView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSCardsView.h 3 | // Google Now Style Card View 4 | // 5 | // Created by R0CKSTAR on 5/21/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RSCardView.h" 11 | 12 | @class RSCardsView; 13 | 14 | @protocol RSCardsViewDelegate 15 | 16 | @required 17 | 18 | - (CGFloat)heightForHeaderInCardsView:(RSCardsView *)cardsView; 19 | 20 | - (CGFloat)heightForFooterInCardsView:(RSCardsView *)cardsView; 21 | 22 | - (CGFloat)heightForSeparatorInCardsView:(RSCardsView *)cardsView; 23 | 24 | - (CGFloat)heightForCoveredRowInCardsView:(RSCardsView *)cardsView; 25 | 26 | - (void)cardViewDidRemoveAtIndexPath:(NSIndexPath *)indexPath; 27 | 28 | // For exchange animation 29 | - (void)cardViewWillExchangeAtIndexPath:(NSIndexPath *)indexPath withIndexPath:(NSIndexPath *)otherIndexPath; 30 | 31 | - (void)cardViewDidExchangeAtIndexPath:(NSIndexPath *)indexPath withIndexPath:(NSIndexPath *)otherIndexPath; 32 | 33 | // For drop animation 34 | - (void)cardViewWillDropAtIndexPath:(NSIndexPath *)indexPath; 35 | 36 | - (void)cardViewDidDropAtIndexPath:(NSIndexPath *)indexPath; 37 | 38 | @end 39 | 40 | @protocol RSCardsViewDataSource 41 | 42 | @required 43 | 44 | - (NSInteger)numberOfSectionsInCardsView:(RSCardsView *)cardsView; 45 | 46 | - (NSInteger)cardsView:(RSCardsView *)cardsView numberOfRowsInSection:(NSInteger)section; 47 | 48 | - (RSCardView *)cardsView:(RSCardsView *)cardsView cardForRowAtIndexPath:(NSIndexPath *)indexPath; 49 | 50 | @end 51 | 52 | // Animation style enum 53 | typedef NS_ENUM (NSUInteger, RSCardsViewAnimationStyle) { 54 | RSCardsViewAnimationStyleExchange = 0, 55 | RSCardsViewAnimationStyleDrop 56 | }; 57 | 58 | @interface RSCardsView : UIScrollView 59 | 60 | @property (nonatomic, assign) id dataSource; 61 | 62 | @property (nonatomic, assign) id delegate; 63 | 64 | @property (nonatomic, assign) RSCardsViewAnimationStyle animationStyle; 65 | 66 | - (void)setNeedsReload; 67 | 68 | - (void)insertCard:(RSCardView *)card; 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Card View/UIView+Folding.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | typedef double (^KeyframeParametrizedBlock)(NSTimeInterval); 5 | 6 | // 7 | // The following constants can be used in case you think its 8 | // useful to exclude nonsense values from being supplied to the 9 | // category. If you want to use the boundary checking, set the 10 | // kbFoldingViewUseBoundsChecking flag to 1, and configure the 11 | // boundary value as you wish. Otherwise, set it to 0. 12 | // 13 | #define kbFoldingViewUseBoundsChecking 1 14 | #define kbFoldingViewMinFolds 1 15 | #define kbFoldingViewMaxFolds 20 16 | #define kbFoldingViewMinDuration 0.2f 17 | #define kbFoldingViewMaxDuration 10.0f 18 | 19 | #pragma mark - 20 | #pragma mark CAKeyframeAnimation Category 21 | // 22 | // CAKeyframeAnimation Category 23 | // 24 | @interface CAKeyframeAnimation (Parametrized) 25 | 26 | + (id)parametrizedAnimationWithKeyPath:(NSString *)path 27 | function:(KeyframeParametrizedBlock)function 28 | fromValue:(CGFloat)fromValue 29 | toValue:(CGFloat)toValue; 30 | 31 | @end 32 | 33 | // 34 | // Animation Constants 35 | // 36 | typedef enum { 37 | KBFoldingViewDirectionFromRight = 0, 38 | KBFoldingViewDirectionFromLeft = 1, 39 | KBFoldingViewDirectionFromTop = 2, 40 | KBFoldingViewDirectionFromBottom = 3, 41 | } KBFoldingViewDirection; 42 | 43 | typedef enum { 44 | KBFoldingTransitionStateIdle = 0, 45 | KBFoldingTransitionStateUpdate = 1, 46 | KBFoldingTransitionStateShowing = 2, 47 | } KBFoldingTransitionState; 48 | 49 | #pragma mark - 50 | #pragma mark UIView Category 51 | // 52 | // UIView Category 53 | // 54 | @interface UIView (Folding) 55 | 56 | @property (nonatomic, readonly) NSUInteger state; 57 | 58 | #pragma mark - 59 | #pragma mark Show Methods 60 | 61 | // Fold the view using specified values 62 | - (void)showFoldingView:(UIView *)view 63 | backgroundColor:(UIColor *)backgroundColor 64 | folds:(NSUInteger)folds 65 | direction:(NSUInteger)direction 66 | duration:(NSTimeInterval)duration 67 | onCompletion:(void(^) (BOOL finished))onCompletion; 68 | 69 | #pragma mark - 70 | #pragma mark Hide Methods 71 | 72 | // Hide the folds using specified values 73 | - (void)hideFoldingView:(UIView *)view 74 | backgroundColor:(UIColor *)backgroundColor 75 | folds:(NSUInteger)folds 76 | direction:(NSUInteger)direction 77 | duration:(NSTimeInterval)duration 78 | onCompletion:(void(^) (BOOL finished))onCompletion; 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /Sample/Sample/RSAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSAppDelegate.m 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSAppDelegate.h" 10 | 11 | #import "RSViewController.h" 12 | 13 | @implementation RSAppDelegate 14 | 15 | - (void)dealloc { 16 | [_window release]; 17 | [_viewController release]; 18 | [super dealloc]; 19 | } 20 | 21 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 22 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 23 | // Override point for customization after application launch. 24 | self.viewController = [[[RSViewController alloc] init] autorelease]; 25 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.viewController]; 26 | navigationController.navigationBarHidden = YES; 27 | self.window.rootViewController = navigationController; 28 | [self.window makeKeyAndVisible]; 29 | return YES; 30 | } 31 | 32 | - (void)applicationWillResignActive:(UIApplication *)application { 33 | // 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. 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidEnterBackground:(UIApplication *)application { 38 | // 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. 39 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 40 | } 41 | 42 | - (void)applicationWillEnterForeground:(UIApplication *)application { 43 | // 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. 44 | } 45 | 46 | - (void)applicationDidBecomeActive:(UIApplication *)application { 47 | // 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. 48 | } 49 | 50 | - (void)applicationWillTerminate:(UIApplication *)application { 51 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Card View/UIColor+Expanded.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Expanded.h 3 | // Additions 4 | // 5 | // Created by Erica Sadun, http://ericasadun.com 6 | // iPhone Developer's Cookbook, 3.0 Edition 7 | // BSD License, Use at your own risk 8 | 9 | #import 10 | 11 | #define SUPPORTS_UNDOCUMENTED_API 0 12 | 13 | @interface UIColor (UIColor_Expanded) 14 | @property (nonatomic, readonly) CGColorSpaceModel colorSpaceModel; 15 | @property (nonatomic, readonly) BOOL canProvideRGBComponents; 16 | @property (nonatomic, readonly) CGFloat red; // Only valid if canProvideRGBComponents is YES 17 | @property (nonatomic, readonly) CGFloat green; // Only valid if canProvideRGBComponents is YES 18 | @property (nonatomic, readonly) CGFloat blue; // Only valid if canProvideRGBComponents is YES 19 | @property (nonatomic, readonly) CGFloat white; // Only valid if colorSpaceModel == kCGColorSpaceModelMonochrome 20 | @property (nonatomic, readonly) CGFloat alpha; 21 | @property (nonatomic, readonly) UInt32 rgbHex; 22 | 23 | - (NSString *)colorSpaceString; 24 | 25 | - (NSArray *)arrayFromRGBAComponents; 26 | 27 | - (BOOL)red:(CGFloat *)r green:(CGFloat *)g blue:(CGFloat *)b alpha:(CGFloat *)a; 28 | 29 | - (UIColor *)colorByLuminanceMapping; 30 | 31 | - (UIColor *)colorByMultiplyingByRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; 32 | - (UIColor *)colorByAddingRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; 33 | - (UIColor *)colorByLighteningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; 34 | - (UIColor *)colorByDarkeningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; 35 | 36 | - (UIColor *)colorByMultiplyingBy:(CGFloat)f; 37 | - (UIColor *)colorByAdding:(CGFloat)f; 38 | - (UIColor *)colorByLighteningTo:(CGFloat)f; 39 | - (UIColor *)colorByDarkeningTo:(CGFloat)f; 40 | 41 | - (UIColor *)colorByMultiplyingByColor:(UIColor *)color; 42 | - (UIColor *)colorByAddingColor:(UIColor *)color; 43 | - (UIColor *)colorByLighteningToColor:(UIColor *)color; 44 | - (UIColor *)colorByDarkeningToColor:(UIColor *)color; 45 | 46 | - (NSString *)stringFromColor; 47 | - (NSString *)hexStringFromColor; 48 | 49 | + (UIColor *)randomColor; 50 | + (UIColor *)colorWithString:(NSString *)stringToConvert; 51 | + (UIColor *)colorWithRGBHex:(UInt32)hex; 52 | + (UIColor *)colorWithARGBHex:(UInt32)hex; 53 | + (UIColor *)colorWithRGBHexString:(NSString *)stringToConvert; 54 | + (UIColor *)colorWithARGBHexString:(NSString *)stringToConvert; 55 | 56 | + (UIColor *)colorWithName:(NSString *)cssColorName; 57 | 58 | @end 59 | 60 | #if SUPPORTS_UNDOCUMENTED_API 61 | // UIColor_Undocumented_Expanded 62 | // Methods which rely on undocumented methods of UIColor 63 | @interface UIColor (UIColor_Undocumented_Expanded) 64 | - (NSString *)fetchStyleString; 65 | - (UIColor *)rgbColor; // Via Poltras 66 | @end 67 | #endif // SUPPORTS_UNDOCUMENTED_API 68 | -------------------------------------------------------------------------------- /Sample/Sample.xcodeproj/xcuserdata/R0CKSTAR.xcuserdatad/xcschemes/Sample.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 | -------------------------------------------------------------------------------- /Sample/Sample/RSViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSViewController.m 3 | // Sample 4 | // 5 | // Created by R0CKSTAR on 6/30/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSViewController.h" 10 | #import "RSSimpleCard.h" 11 | #import "RSSimpleCardView.h" 12 | #import "RSCardsView.h" 13 | 14 | @interface RSProfileCard : RSSimpleCard 15 | @end 16 | @implementation RSProfileCard 17 | 18 | + (CGFloat)contentHeight { 19 | return 80; 20 | } 21 | 22 | @end 23 | 24 | @interface RSProjectCard : RSSimpleCard 25 | @end 26 | @implementation RSProjectCard 27 | @end 28 | 29 | @interface RSProfileCardView : RSSimpleCardView 30 | @end 31 | @implementation RSProfileCardView 32 | @end 33 | 34 | @interface RSProjectCardView : RSSimpleCardView 35 | @end 36 | @implementation RSProjectCardView 37 | @end 38 | 39 | @interface RSViewController () 40 | { 41 | NSMutableArray *_data; 42 | } 43 | 44 | @end 45 | 46 | @implementation RSViewController 47 | 48 | #pragma mark - 49 | 50 | - (NSMutableArray *)data { 51 | if (!_data) { 52 | NSMutableArray *profiles = [NSMutableArray arrayWithObjects: 53 | [[[RSProfileCard alloc] initWithText:nil] autorelease], 54 | [[[RSProfileCard alloc] initWithText:nil] autorelease], 55 | [[[RSProfileCard alloc] initWithText:nil] autorelease], nil]; 56 | NSMutableArray *projects = [NSMutableArray arrayWithObjects: 57 | [[[RSProjectCard alloc] initWithText:nil] autorelease], 58 | [[[RSProjectCard alloc] initWithText:nil] autorelease], 59 | [[[RSProjectCard alloc] initWithText:nil] autorelease], 60 | [[[RSProjectCard alloc] initWithText:nil] autorelease], nil]; 61 | _data = [[NSMutableArray arrayWithObjects:profiles, projects, nil] retain]; 62 | } 63 | 64 | return _data; 65 | } 66 | 67 | #pragma mark - 68 | 69 | - (void)loadView { 70 | RSCardsView *view = [[[RSCardsView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame] autorelease]; 71 | view.delegate = self; 72 | view.dataSource = self; 73 | view.animationStyle = RSCardsViewAnimationStyleExchange; 74 | self.view = view; 75 | } 76 | 77 | - (void)viewDidLoad { 78 | [super viewDidLoad]; 79 | 80 | // Do any additional setup after loading the view, typically from a nib. 81 | if (0) { 82 | [(RSCardsView *)self.view setNeedsReload]; 83 | } else { 84 | for (int i = 0; i < [[self data] count]; i++) { 85 | for (int j = 0; j < [[self data][i] count]; j++) { 86 | RSSimpleCardView *cardView = nil; 87 | 88 | if (i == 0) { 89 | cardView = [[[RSProfileCardView alloc] initWithFrame:self.view.bounds] autorelease]; 90 | } else { 91 | cardView = [[[RSProjectCardView alloc] initWithFrame:self.view.bounds] autorelease]; 92 | } 93 | 94 | RSSimpleCard *card = [self data][i][j]; 95 | cardView.delegate = (RSCardsView *)self.view; 96 | [cardView setText:[card text]]; 97 | [cardView setContentViewHeight:[[card class] contentHeight] animated:NO]; 98 | [cardView setSettingsViewHeight:[[card class] settingsHeight]]; 99 | [cardView setNeedsLayout]; 100 | 101 | [(RSCardsView *)self.view insertCard : cardView]; 102 | } 103 | } 104 | 105 | id o = [self data][0]; 106 | [o retain]; 107 | [[self data] removeObjectAtIndex:0]; 108 | [[self data] addObject:o]; 109 | [o release]; 110 | } 111 | } 112 | 113 | - (void)didReceiveMemoryWarning { 114 | [super didReceiveMemoryWarning]; 115 | // Dispose of any resources that can be recreated. 116 | } 117 | 118 | - (void)dealloc { 119 | [_data removeAllObjects]; 120 | [_data release]; 121 | [super dealloc]; 122 | } 123 | 124 | #pragma mark - RSCardsViewDataSource 125 | 126 | - (NSInteger)numberOfSectionsInCardsView:(RSCardsView *)cardsView { 127 | return [[self data] count]; 128 | } 129 | 130 | - (NSInteger)cardsView:(RSCardsView *)cardsView numberOfRowsInSection:(NSInteger)section { 131 | return [[self data][section] count]; 132 | } 133 | 134 | - (RSCardView *)cardsView:(RSCardsView *)cardsView cardForRowAtIndexPath:(NSIndexPath *)indexPath { 135 | RSSimpleCardView *cardView = [[[RSSimpleCardView alloc] initWithFrame:self.view.bounds] autorelease]; 136 | 137 | RSSimpleCard *card = [self data][[indexPath section]][[indexPath row]]; 138 | 139 | cardView.delegate = cardsView; 140 | [cardView setText:[card text]]; 141 | [cardView setContentViewHeight:[[card class] contentHeight] animated:NO]; 142 | [cardView setSettingsViewHeight:[[card class] settingsHeight]]; 143 | [cardView setNeedsLayout]; 144 | 145 | return cardView; 146 | } 147 | 148 | #pragma mark - RSCardsViewDelegate 149 | 150 | - (CGFloat)heightForHeaderInCardsView:(RSCardsView *)cardsView { 151 | return 10; 152 | } 153 | 154 | - (CGFloat)heightForFooterInCardsView:(RSCardsView *)cardsView { 155 | return 10; 156 | } 157 | 158 | - (CGFloat)heightForSeparatorInCardsView:(RSCardsView *)cardsView { 159 | return 5; 160 | } 161 | 162 | - (CGFloat)heightForCoveredRowInCardsView:(RSCardsView *)cardsView { 163 | return 60; 164 | } 165 | 166 | - (void)cardViewDidRemoveAtIndexPath:(NSIndexPath *)indexPath { 167 | NSMutableArray *section = [self data][indexPath.section]; 168 | 169 | [section removeObjectAtIndex:indexPath.row]; 170 | 171 | if (section.count <= 0) { 172 | [[self data] removeObjectAtIndex:indexPath.section]; 173 | } 174 | } 175 | 176 | // Exchange animation callback 177 | - (void)cardViewWillExchangeAtIndexPath:(NSIndexPath *)indexPath withIndexPath:(NSIndexPath *)otherIndexPath { 178 | } 179 | 180 | - (void)cardViewDidExchangeAtIndexPath:(NSIndexPath *)indexPath withIndexPath:(NSIndexPath *)otherIndexPath { 181 | if (indexPath.section == otherIndexPath.section) { 182 | NSMutableArray *section = [self data][indexPath.section]; 183 | [section exchangeObjectAtIndex:indexPath.row withObjectAtIndex:otherIndexPath.row]; 184 | } 185 | } 186 | 187 | // Drop animation callback 188 | - (void)cardViewWillDropAtIndexPath:(NSIndexPath *)indexPath { 189 | } 190 | 191 | - (void)cardViewDidDropAtIndexPath:(NSIndexPath *)indexPath { 192 | NSMutableArray *section = [self data][indexPath.section]; 193 | id object = [section[indexPath.row] retain]; 194 | 195 | [section removeObjectAtIndex:indexPath.row]; 196 | [section addObject:object]; 197 | [object release]; 198 | } 199 | 200 | @end 201 | -------------------------------------------------------------------------------- /Card View/RSCardView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSCardView.m 3 | // Google Now Style Card View 4 | // 5 | // Created by R0CKSTAR on 5/21/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSCardView.h" 10 | #import 11 | #import "UIView+Folding.h" 12 | #import "UIColor+Expanded.h" 13 | 14 | // Percentage limit to trigger certain action 15 | static CGFloat const kRSActionThreshold = 0.70; 16 | // Maximum bounce amplitude 17 | static CGFloat const kRSBounceAmplitude = 20.0; 18 | // Duration of the first part of the bounce animation 19 | static NSTimeInterval const kRSBounceDurationForth = 0.2; 20 | // Duration of the second part of the bounce animation 21 | static NSTimeInterval const kRSBounceDurationBack = 0.1; 22 | // Lowest duration when swipping the cell because we try to simulate velocity 23 | static NSTimeInterval const kRSDurationLowLimit = 0.25; 24 | // Highest duration when swipping the cell because we try to simulate velocity 25 | static NSTimeInterval const kRSDurationHighLimit = 0.1; 26 | 27 | static const int kContentViewShadowRadius = 2; 28 | #define kContentViewShadowOffset CGSizeMake(0, 1) 29 | #define kContentViewMargin UIEdgeInsetsMake(kContentViewShadowRadius * 2, 10, kContentViewShadowRadius * 2 + kContentViewShadowOffset.height /*adjust*/ + 2, 10) 30 | 31 | @interface RSCardView () { 32 | UIPanGestureRecognizer *_panGestureRecognizer; 33 | UITapGestureRecognizer *_tapGestureRecognizer; 34 | } 35 | 36 | @end 37 | 38 | @implementation RSCardView 39 | 40 | #pragma mark - Private 41 | 42 | - (void)toggleSettings:(UIButton *)button { 43 | button.selected = !button.selected; 44 | 45 | CGRect frame = _settingsView.frame; 46 | 47 | if (button.selected) { 48 | _settingsView.hidden = NO; 49 | frame.origin.y = (_contentView.frame.origin.y + _contentView.frame.size.height); 50 | } 51 | 52 | _settingsView.frame = frame; 53 | 54 | [UIView animateWithDuration:0.3 55 | delay:0 56 | options:UIViewAnimationOptionCurveEaseInOut 57 | animations: ^{ 58 | CGRect frame = self.frame; 59 | 60 | if (button.selected) { 61 | frame.size.height += _settingsView.frame.size.height; 62 | } else { 63 | frame.size.height -= _settingsView.frame.size.height; 64 | } 65 | 66 | self.frame = frame; 67 | 68 | if (_delegate && [_delegate respondsToSelector:@selector(didChangeFrame:)]) { 69 | [_delegate didChangeFrame:self]; 70 | } 71 | } 72 | 73 | completion: ^(BOOL finished) { 74 | if (!button.selected) { 75 | _settingsView.hidden = YES; 76 | } 77 | }]; 78 | 79 | UIColor *backgroundColor = nil; 80 | 81 | if (_delegate && [_delegate respondsToSelector:@selector(superviewBackgroundColor)]) { 82 | backgroundColor = [_delegate superviewBackgroundColor]; 83 | } else { 84 | backgroundColor = [UIColor lightGrayColor]; 85 | } 86 | 87 | if (button.selected) { 88 | [_contentView showFoldingView:_settingsView 89 | backgroundColor:backgroundColor 90 | folds:1 91 | direction:KBFoldingViewDirectionFromTop 92 | duration:0.3 93 | onCompletion:nil]; 94 | } else { 95 | [_contentView hideFoldingView:_settingsView 96 | backgroundColor:backgroundColor 97 | folds:1 98 | direction:KBFoldingViewDirectionFromTop 99 | duration:0.3 100 | onCompletion:nil]; 101 | } 102 | } 103 | 104 | - (void)settingsButtonClicked:(UIButton *)button { 105 | if (_delegate && [_delegate respondsToSelector:@selector(canToggleSettings:)]) { 106 | if ([_delegate canToggleSettings:self]) { 107 | [self toggleSettings:button]; 108 | } else { 109 | if (_delegate && [_delegate respondsToSelector:@selector(didTapOnCard:)]) { 110 | [_delegate didTapOnCard:self]; 111 | } 112 | 113 | _shouldOpenSettingsLater = YES; 114 | } 115 | } 116 | } 117 | 118 | #pragma mark - UIView 119 | 120 | - (id)initWithFrame:(CGRect)frame { 121 | self = [super initWithFrame:frame]; 122 | 123 | if (self) { 124 | // Initialization code 125 | self.clipsToBounds = YES; 126 | self.backgroundColor = [UIColor clearColor]; 127 | 128 | _settingsView = [[[UIView alloc] initWithFrame:CGRectMake(kContentViewMargin.left, 0, self.bounds.size.width - kContentViewMargin.left - kContentViewMargin.right, 0)] autorelease]; 129 | _settingsView.autoresizingMask = UIViewAutoresizingFlexibleWidth; 130 | _settingsView.backgroundColor = [UIColor lightGrayColor]; 131 | _settingsView.hidden = YES; 132 | [self addSubview:_settingsView]; 133 | 134 | _contentView = [[[UIView alloc] initWithFrame:CGRectMake(kContentViewMargin.left, kContentViewMargin.top, self.bounds.size.width - kContentViewMargin.left - kContentViewMargin.right, 0)] autorelease]; 135 | _contentView.layer.anchorPoint = CGPointMake(0, 1); 136 | CGRect frame = _contentView.frame; 137 | frame.origin.x = frame.origin.x - roundf(frame.size.width / 2.f); 138 | _contentView.frame = frame; 139 | _contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth; 140 | _contentView.backgroundColor = [UIColor whiteColor]; 141 | _contentView.layer.borderColor = [UIColor darkGrayColor].CGColor; 142 | _contentView.layer.borderWidth = 1; 143 | _contentView.layer.masksToBounds = NO; 144 | _contentView.layer.shadowColor = [UIColor colorWithRGBHex:0x5e5c5c].CGColor; 145 | _contentView.layer.shadowOpacity = 0.75; 146 | _contentView.layer.shadowRadius = kContentViewShadowRadius; 147 | _contentView.layer.shadowOffset = kContentViewShadowOffset; 148 | _contentView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:_contentView.bounds] CGPath]; 149 | _contentView.layer.rasterizationScale = [[UIScreen mainScreen] scale]; 150 | _contentView.layer.shouldRasterize = YES; 151 | [self addSubview:_contentView]; 152 | 153 | _settingsButton = [UIButton buttonWithType:UIButtonTypeCustom]; 154 | [_settingsButton setImage:[UIImage imageNamed:@"Settings"] forState:UIControlStateNormal]; 155 | [_settingsButton addTarget:self action:@selector(settingsButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; 156 | [_settingsButton sizeToFit]; 157 | [_contentView addSubview:_settingsButton]; 158 | 159 | _panGestureRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureRecognizer:)] autorelease]; 160 | _panGestureRecognizer.delegate = self; 161 | [self addGestureRecognizer:_panGestureRecognizer]; 162 | 163 | _tapGestureRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGestureRecognizer:)] autorelease]; 164 | _tapGestureRecognizer.delegate = self; 165 | [self addGestureRecognizer:_tapGestureRecognizer]; 166 | } 167 | 168 | return self; 169 | } 170 | 171 | - (void)layoutSubviews { 172 | [super layoutSubviews]; 173 | 174 | CGRect frame = _settingsButton.frame; 175 | frame.origin.x = _contentView.bounds.size.width - frame.size.width - 5; 176 | frame.origin.y = 5; 177 | _settingsButton.frame = frame; 178 | } 179 | 180 | #pragma mark - Handle Gestures 181 | 182 | - (void)handleTapGestureRecognizer:(UITapGestureRecognizer *)gestureRecognizer { 183 | if (_tapGestureRecognizer == gestureRecognizer) { 184 | if ([gestureRecognizer state] == UIGestureRecognizerStateRecognized) { 185 | if (_delegate && [_delegate respondsToSelector:@selector(didTapOnCard:)]) { 186 | [_delegate didTapOnCard:self]; 187 | } 188 | } 189 | } 190 | } 191 | 192 | - (void)handlePanGestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer { 193 | if (_panGestureRecognizer == gestureRecognizer) { 194 | UIGestureRecognizerState state = [gestureRecognizer state]; 195 | CGPoint translation = [gestureRecognizer translationInView:self]; 196 | CGPoint velocity = [gestureRecognizer velocityInView:self]; 197 | CGFloat percentage = [self percentageWithOffset:CGRectGetMinX(self.frame) relativeToWidth:CGRectGetWidth(self.bounds)]; 198 | NSTimeInterval animationDuration = [self animationDurationWithVelocity:velocity]; 199 | RSCardViewSwipeDirection direction = [self directionWithPercentage:percentage]; 200 | 201 | if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged) { 202 | CGPoint center = { self.center.x + translation.x, self.center.y }; 203 | [self setCenter:center]; 204 | [self updateAlpha]; 205 | [gestureRecognizer setTranslation:CGPointZero inView:self]; 206 | } else if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled) { 207 | if (direction != RSCardViewSwipeDirectionCenter) { 208 | [self moveWithDuration:animationDuration andDirection:direction]; 209 | } else { 210 | [self bounceWithDistance:kRSBounceAmplitude * percentage]; 211 | } 212 | } 213 | } 214 | } 215 | 216 | #pragma mark - UIGestureRecognizerDelegate 217 | 218 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { 219 | if (_panGestureRecognizer == gestureRecognizer) { 220 | UIPanGestureRecognizer *panGestureRecognizer = (UIPanGestureRecognizer *)gestureRecognizer; 221 | CGPoint velocity = [panGestureRecognizer velocityInView:self]; 222 | return fabsf(velocity.x) > fabsf(velocity.y); 223 | } 224 | 225 | return YES; 226 | } 227 | 228 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { 229 | if (_tapGestureRecognizer == gestureRecognizer) { 230 | if ([NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"]) { 231 | return NO; 232 | } 233 | } 234 | 235 | return YES; 236 | } 237 | 238 | #pragma mark - Utils 239 | 240 | - (CGFloat)percentageWithOffset:(CGFloat)offset relativeToWidth:(CGFloat)width { 241 | CGFloat percentage = offset / width; 242 | 243 | if (percentage < -1.0) percentage = -1.0; 244 | else if (percentage > 1.0) percentage = 1.0; 245 | 246 | return percentage; 247 | } 248 | 249 | - (NSTimeInterval)animationDurationWithVelocity:(CGPoint)velocity { 250 | CGFloat width = CGRectGetWidth(self.bounds); 251 | NSTimeInterval animationDurationDiff = kRSDurationHighLimit - kRSDurationLowLimit; 252 | CGFloat horizontalVelocity = velocity.x; 253 | 254 | if (horizontalVelocity < -width) horizontalVelocity = -width; 255 | else if (horizontalVelocity > width) horizontalVelocity = width; 256 | 257 | return (kRSDurationHighLimit + kRSDurationLowLimit) - fabs(((horizontalVelocity / width) * animationDurationDiff)); 258 | } 259 | 260 | - (RSCardViewSwipeDirection)directionWithPercentage:(CGFloat)percentage { 261 | if (percentage < -kRSActionThreshold) { 262 | return RSCardViewSwipeDirectionLeft; 263 | } else if (percentage > kRSActionThreshold) { 264 | return RSCardViewSwipeDirectionRight; 265 | } else { 266 | return RSCardViewSwipeDirectionCenter; 267 | } 268 | } 269 | 270 | - (void)updateAlpha { 271 | self.alpha = (self.frame.size.width - fabsf(self.frame.origin.x)) / self.frame.size.width; 272 | } 273 | 274 | #pragma mark - Movement 275 | 276 | - (void)moveWithDuration:(NSTimeInterval)duration andDirection:(RSCardViewSwipeDirection)direction { 277 | CGRect frame = self.frame; 278 | 279 | if (direction == RSCardViewSwipeDirectionLeft) { 280 | frame.origin.x = -CGRectGetWidth(self.bounds); 281 | } else { 282 | frame.origin.x = CGRectGetWidth(self.bounds); 283 | } 284 | 285 | [UIView animateWithDuration:duration 286 | delay:0.0 287 | options:(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction) 288 | animations: ^{ 289 | [self setFrame:frame]; 290 | [self updateAlpha]; 291 | } 292 | 293 | completion: ^(BOOL finished) { 294 | [self removeFromSuperview]; 295 | 296 | if (_delegate && [_delegate respondsToSelector:@selector(didRemoveFromSuperview:)]) { 297 | [_delegate didRemoveFromSuperview:self]; 298 | } 299 | }]; 300 | } 301 | 302 | - (void)bounceWithDistance:(CGFloat)bounceDistance { 303 | [UIView animateWithDuration:kRSBounceDurationForth 304 | delay:0 305 | options:(UIViewAnimationOptionCurveEaseOut) 306 | animations: ^{ 307 | CGRect frame = self.frame; 308 | frame.origin.x = -bounceDistance; 309 | [self setFrame:frame]; 310 | [self updateAlpha]; 311 | } 312 | 313 | completion: ^(BOOL forthFinished) { 314 | [UIView animateWithDuration:kRSBounceDurationBack 315 | delay:0 316 | options:UIViewAnimationOptionCurveEaseIn 317 | animations: ^{ 318 | CGRect frame = self.frame; 319 | frame.origin.x = 0; 320 | [self setFrame:frame]; 321 | } 322 | 323 | completion: ^(BOOL backFinished) { 324 | }]; 325 | }]; 326 | } 327 | 328 | #pragma mark - Public 329 | 330 | - (void)toggleSettings { 331 | [self toggleSettings:_settingsButton]; 332 | } 333 | 334 | - (BOOL)isSettingsVisible { 335 | return _settingsButton.selected; 336 | } 337 | 338 | - (void)setContentViewHeight:(CGFloat)height animated:(BOOL)animated { 339 | void (^ updateFrame)() = ^() { 340 | CGRect frame = _contentView.frame; 341 | frame.size.height = height; 342 | _contentView.frame = frame; 343 | 344 | frame = self.frame; 345 | frame.size.height = height + kContentViewMargin.top + kContentViewMargin.bottom; 346 | self.frame = frame; 347 | 348 | _contentView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:_contentView.bounds] CGPath]; 349 | }; 350 | 351 | if (animated) { 352 | [UIView animateWithDuration:0.3 353 | delay:0 354 | options:UIViewAnimationOptionCurveEaseInOut 355 | animations: ^{ 356 | updateFrame(); 357 | } 358 | 359 | completion: ^(BOOL finished) { 360 | }]; 361 | } else { 362 | updateFrame(); 363 | } 364 | } 365 | 366 | - (void)setSettingsViewHeight:(CGFloat)settingsViewHeight { 367 | CGRect frame = _settingsView.frame; 368 | 369 | frame.size.height = settingsViewHeight; 370 | frame.origin.y = (_contentView.frame.origin.y + _contentView.frame.size.height - frame.size.height); 371 | _settingsView.frame = frame; 372 | } 373 | 374 | #pragma mark - Insert animation 375 | 376 | - (void)insertAnimation { 377 | _contentView.layer.opacity = 0; 378 | _contentView.layer.transform = CATransform3DTranslate(CATransform3DMakeRotation(M_PI_4 / 4.f, 0, 0, 1), 0, roundf(_contentView.bounds.size.height / 2.f), 0); 379 | 380 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; 381 | animation.fromValue = [NSValue valueWithCATransform3D:_contentView.layer.transform]; 382 | animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 383 | animation.fillMode = kCAFillModeForwards; 384 | animation.duration = 0.5; 385 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 386 | animation.delegate = self; 387 | _contentView.layer.transform = CATransform3DIdentity; 388 | [_contentView.layer addAnimation:animation forKey:@"rotation"]; 389 | 390 | animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 391 | animation.fromValue = [NSNumber numberWithInt:0]; 392 | animation.toValue = [NSNumber numberWithInt:1]; 393 | animation.fillMode = kCAFillModeForwards; 394 | animation.duration = 0.5; 395 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 396 | _contentView.layer.opacity = 1; 397 | [_contentView.layer addAnimation:animation forKey:@"opacity"]; 398 | } 399 | 400 | #pragma mark - CAAnimationDelegate 401 | 402 | - (void)animationDidStart:(CAAnimation *)anim { 403 | if (_delegate && [_delegate respondsToSelector:@selector(insertAnimationDidStart)]) { 404 | [_delegate insertAnimationDidStart]; 405 | } 406 | } 407 | 408 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { 409 | if (_delegate && [_delegate respondsToSelector:@selector(insertAnimationDidStop)]) { 410 | [_delegate insertAnimationDidStop]; 411 | } 412 | } 413 | 414 | @end 415 | -------------------------------------------------------------------------------- /Sample/Sample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D0EB955F17D86CA9008AEBB7 /* Settings@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0EB955E17D86CA9008AEBB7 /* Settings@2x.png */; }; 11 | D0F2B1AA1780511E002053BA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F2B1A91780511E002053BA /* UIKit.framework */; }; 12 | D0F2B1AC1780511E002053BA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F2B1AB1780511E002053BA /* Foundation.framework */; }; 13 | D0F2B1AE1780511E002053BA /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F2B1AD1780511E002053BA /* CoreGraphics.framework */; }; 14 | D0F2B1B41780511E002053BA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0F2B1B21780511E002053BA /* InfoPlist.strings */; }; 15 | D0F2B1B61780511E002053BA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1B51780511E002053BA /* main.m */; }; 16 | D0F2B1BA1780511E002053BA /* RSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1B91780511E002053BA /* RSAppDelegate.m */; }; 17 | D0F2B1BC1780511E002053BA /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = D0F2B1BB1780511E002053BA /* Default.png */; }; 18 | D0F2B1BE1780511E002053BA /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0F2B1BD1780511E002053BA /* Default@2x.png */; }; 19 | D0F2B1C01780511E002053BA /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0F2B1BF1780511E002053BA /* Default-568h@2x.png */; }; 20 | D0F2B1C31780511E002053BA /* RSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1C21780511E002053BA /* RSViewController.m */; }; 21 | D0F2B1D7178051A3002053BA /* RSCard.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1CE178051A3002053BA /* RSCard.m */; }; 22 | D0F2B1D8178051A3002053BA /* RSCardsView.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1D0178051A3002053BA /* RSCardsView.m */; }; 23 | D0F2B1D9178051A3002053BA /* RSCardView.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1D2178051A3002053BA /* RSCardView.m */; }; 24 | D0F2B1DA178051A3002053BA /* UIColor+Expanded.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1D4178051A3002053BA /* UIColor+Expanded.m */; }; 25 | D0F2B1DB178051A3002053BA /* UIView+Folding.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1D6178051A3002053BA /* UIView+Folding.m */; }; 26 | D0F2B1DD1780570A002053BA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F2B1DC1780570A002053BA /* QuartzCore.framework */; }; 27 | D0F2B1E017805C19002053BA /* RSSimpleCard.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1DF17805C19002053BA /* RSSimpleCard.m */; }; 28 | D0F2B1E317805C2A002053BA /* RSSimpleCardView.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F2B1E217805C2A002053BA /* RSSimpleCardView.m */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | D0EB955E17D86CA9008AEBB7 /* Settings@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Settings@2x.png"; sourceTree = ""; }; 33 | D0F2B1A61780511E002053BA /* Google Now Style Card View Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Google Now Style Card View Sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | D0F2B1A91780511E002053BA /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 35 | D0F2B1AB1780511E002053BA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 36 | D0F2B1AD1780511E002053BA /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 37 | D0F2B1B11780511E002053BA /* Sample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Sample-Info.plist"; sourceTree = ""; }; 38 | D0F2B1B31780511E002053BA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 39 | D0F2B1B51780511E002053BA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 40 | D0F2B1B71780511E002053BA /* Sample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Sample-Prefix.pch"; sourceTree = ""; }; 41 | D0F2B1B81780511E002053BA /* RSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RSAppDelegate.h; sourceTree = ""; }; 42 | D0F2B1B91780511E002053BA /* RSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RSAppDelegate.m; sourceTree = ""; }; 43 | D0F2B1BB1780511E002053BA /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 44 | D0F2B1BD1780511E002053BA /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 45 | D0F2B1BF1780511E002053BA /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 46 | D0F2B1C11780511E002053BA /* RSViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RSViewController.h; sourceTree = ""; }; 47 | D0F2B1C21780511E002053BA /* RSViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RSViewController.m; sourceTree = ""; }; 48 | D0F2B1CD178051A3002053BA /* RSCard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSCard.h; sourceTree = ""; }; 49 | D0F2B1CE178051A3002053BA /* RSCard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSCard.m; sourceTree = ""; }; 50 | D0F2B1CF178051A3002053BA /* RSCardsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSCardsView.h; sourceTree = ""; }; 51 | D0F2B1D0178051A3002053BA /* RSCardsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSCardsView.m; sourceTree = ""; }; 52 | D0F2B1D1178051A3002053BA /* RSCardView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSCardView.h; sourceTree = ""; }; 53 | D0F2B1D2178051A3002053BA /* RSCardView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSCardView.m; sourceTree = ""; }; 54 | D0F2B1D3178051A3002053BA /* UIColor+Expanded.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+Expanded.h"; sourceTree = ""; }; 55 | D0F2B1D4178051A3002053BA /* UIColor+Expanded.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Expanded.m"; sourceTree = ""; }; 56 | D0F2B1D5178051A3002053BA /* UIView+Folding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Folding.h"; sourceTree = ""; }; 57 | D0F2B1D6178051A3002053BA /* UIView+Folding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+Folding.m"; sourceTree = ""; }; 58 | D0F2B1DC1780570A002053BA /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 59 | D0F2B1DE17805C19002053BA /* RSSimpleCard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSSimpleCard.h; sourceTree = ""; }; 60 | D0F2B1DF17805C19002053BA /* RSSimpleCard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSSimpleCard.m; sourceTree = ""; }; 61 | D0F2B1E117805C2A002053BA /* RSSimpleCardView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSSimpleCardView.h; sourceTree = ""; }; 62 | D0F2B1E217805C2A002053BA /* RSSimpleCardView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSSimpleCardView.m; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | D0F2B1A31780511E002053BA /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | D0F2B1DD1780570A002053BA /* QuartzCore.framework in Frameworks */, 71 | D0F2B1AA1780511E002053BA /* UIKit.framework in Frameworks */, 72 | D0F2B1AC1780511E002053BA /* Foundation.framework in Frameworks */, 73 | D0F2B1AE1780511E002053BA /* CoreGraphics.framework in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | /* End PBXFrameworksBuildPhase section */ 78 | 79 | /* Begin PBXGroup section */ 80 | D0F2B19D1780511E002053BA = { 81 | isa = PBXGroup; 82 | children = ( 83 | D0F2B1AF1780511E002053BA /* Sample */, 84 | D0F2B1A81780511E002053BA /* Frameworks */, 85 | D0F2B1A71780511E002053BA /* Products */, 86 | ); 87 | sourceTree = ""; 88 | }; 89 | D0F2B1A71780511E002053BA /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | D0F2B1A61780511E002053BA /* Google Now Style Card View Sample.app */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | D0F2B1A81780511E002053BA /* Frameworks */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | D0F2B1DC1780570A002053BA /* QuartzCore.framework */, 101 | D0F2B1A91780511E002053BA /* UIKit.framework */, 102 | D0F2B1AB1780511E002053BA /* Foundation.framework */, 103 | D0F2B1AD1780511E002053BA /* CoreGraphics.framework */, 104 | ); 105 | name = Frameworks; 106 | sourceTree = ""; 107 | }; 108 | D0F2B1AF1780511E002053BA /* Sample */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | D0F2B1CC178051A3002053BA /* Card View */, 112 | D0F2B1B81780511E002053BA /* RSAppDelegate.h */, 113 | D0F2B1B91780511E002053BA /* RSAppDelegate.m */, 114 | D0F2B1C11780511E002053BA /* RSViewController.h */, 115 | D0F2B1C21780511E002053BA /* RSViewController.m */, 116 | D0F2B1DE17805C19002053BA /* RSSimpleCard.h */, 117 | D0F2B1DF17805C19002053BA /* RSSimpleCard.m */, 118 | D0F2B1E117805C2A002053BA /* RSSimpleCardView.h */, 119 | D0F2B1E217805C2A002053BA /* RSSimpleCardView.m */, 120 | D0F2B1B01780511E002053BA /* Supporting Files */, 121 | ); 122 | path = Sample; 123 | sourceTree = ""; 124 | }; 125 | D0F2B1B01780511E002053BA /* Supporting Files */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | D0F2B1B11780511E002053BA /* Sample-Info.plist */, 129 | D0F2B1B21780511E002053BA /* InfoPlist.strings */, 130 | D0F2B1B51780511E002053BA /* main.m */, 131 | D0F2B1B71780511E002053BA /* Sample-Prefix.pch */, 132 | D0F2B1BB1780511E002053BA /* Default.png */, 133 | D0F2B1BD1780511E002053BA /* Default@2x.png */, 134 | D0F2B1BF1780511E002053BA /* Default-568h@2x.png */, 135 | ); 136 | name = "Supporting Files"; 137 | sourceTree = ""; 138 | }; 139 | D0F2B1CC178051A3002053BA /* Card View */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | D0EB955E17D86CA9008AEBB7 /* Settings@2x.png */, 143 | D0F2B1CD178051A3002053BA /* RSCard.h */, 144 | D0F2B1CE178051A3002053BA /* RSCard.m */, 145 | D0F2B1CF178051A3002053BA /* RSCardsView.h */, 146 | D0F2B1D0178051A3002053BA /* RSCardsView.m */, 147 | D0F2B1D1178051A3002053BA /* RSCardView.h */, 148 | D0F2B1D2178051A3002053BA /* RSCardView.m */, 149 | D0F2B1D3178051A3002053BA /* UIColor+Expanded.h */, 150 | D0F2B1D4178051A3002053BA /* UIColor+Expanded.m */, 151 | D0F2B1D5178051A3002053BA /* UIView+Folding.h */, 152 | D0F2B1D6178051A3002053BA /* UIView+Folding.m */, 153 | ); 154 | name = "Card View"; 155 | path = "../../Card View"; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXNativeTarget section */ 161 | D0F2B1A51780511E002053BA /* Sample */ = { 162 | isa = PBXNativeTarget; 163 | buildConfigurationList = D0F2B1C91780511E002053BA /* Build configuration list for PBXNativeTarget "Sample" */; 164 | buildPhases = ( 165 | D0F2B1A21780511E002053BA /* Sources */, 166 | D0F2B1A31780511E002053BA /* Frameworks */, 167 | D0F2B1A41780511E002053BA /* Resources */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | ); 173 | name = Sample; 174 | productName = Sample; 175 | productReference = D0F2B1A61780511E002053BA /* Google Now Style Card View Sample.app */; 176 | productType = "com.apple.product-type.application"; 177 | }; 178 | /* End PBXNativeTarget section */ 179 | 180 | /* Begin PBXProject section */ 181 | D0F2B19E1780511E002053BA /* Project object */ = { 182 | isa = PBXProject; 183 | attributes = { 184 | CLASSPREFIX = RS; 185 | LastUpgradeCheck = 0460; 186 | ORGANIZATIONNAME = P.D.Q.; 187 | }; 188 | buildConfigurationList = D0F2B1A11780511E002053BA /* Build configuration list for PBXProject "Sample" */; 189 | compatibilityVersion = "Xcode 3.2"; 190 | developmentRegion = English; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | ); 195 | mainGroup = D0F2B19D1780511E002053BA; 196 | productRefGroup = D0F2B1A71780511E002053BA /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | D0F2B1A51780511E002053BA /* Sample */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | D0F2B1A41780511E002053BA /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | D0F2B1B41780511E002053BA /* InfoPlist.strings in Resources */, 211 | D0F2B1BC1780511E002053BA /* Default.png in Resources */, 212 | D0EB955F17D86CA9008AEBB7 /* Settings@2x.png in Resources */, 213 | D0F2B1BE1780511E002053BA /* Default@2x.png in Resources */, 214 | D0F2B1C01780511E002053BA /* Default-568h@2x.png in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXSourcesBuildPhase section */ 221 | D0F2B1A21780511E002053BA /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | D0F2B1B61780511E002053BA /* main.m in Sources */, 226 | D0F2B1BA1780511E002053BA /* RSAppDelegate.m in Sources */, 227 | D0F2B1C31780511E002053BA /* RSViewController.m in Sources */, 228 | D0F2B1D7178051A3002053BA /* RSCard.m in Sources */, 229 | D0F2B1D8178051A3002053BA /* RSCardsView.m in Sources */, 230 | D0F2B1D9178051A3002053BA /* RSCardView.m in Sources */, 231 | D0F2B1DA178051A3002053BA /* UIColor+Expanded.m in Sources */, 232 | D0F2B1DB178051A3002053BA /* UIView+Folding.m in Sources */, 233 | D0F2B1E017805C19002053BA /* RSSimpleCard.m in Sources */, 234 | D0F2B1E317805C2A002053BA /* RSSimpleCardView.m in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin PBXVariantGroup section */ 241 | D0F2B1B21780511E002053BA /* InfoPlist.strings */ = { 242 | isa = PBXVariantGroup; 243 | children = ( 244 | D0F2B1B31780511E002053BA /* en */, 245 | ); 246 | name = InfoPlist.strings; 247 | sourceTree = ""; 248 | }; 249 | /* End PBXVariantGroup section */ 250 | 251 | /* Begin XCBuildConfiguration section */ 252 | D0F2B1C71780511E002053BA /* Debug */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 257 | CLANG_CXX_LIBRARY = "libc++"; 258 | CLANG_WARN_CONSTANT_CONVERSION = YES; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 263 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 264 | COPY_PHASE_STRIP = NO; 265 | GCC_C_LANGUAGE_STANDARD = gnu99; 266 | GCC_DYNAMIC_NO_PIC = NO; 267 | GCC_OPTIMIZATION_LEVEL = 0; 268 | GCC_PREPROCESSOR_DEFINITIONS = ( 269 | "DEBUG=1", 270 | "$(inherited)", 271 | ); 272 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 277 | ONLY_ACTIVE_ARCH = YES; 278 | SDKROOT = iphoneos; 279 | }; 280 | name = Debug; 281 | }; 282 | D0F2B1C81780511E002053BA /* Release */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ALWAYS_SEARCH_USER_PATHS = NO; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 287 | CLANG_CXX_LIBRARY = "libc++"; 288 | CLANG_WARN_CONSTANT_CONVERSION = YES; 289 | CLANG_WARN_EMPTY_BODY = YES; 290 | CLANG_WARN_ENUM_CONVERSION = YES; 291 | CLANG_WARN_INT_CONVERSION = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 294 | COPY_PHASE_STRIP = YES; 295 | GCC_C_LANGUAGE_STANDARD = gnu99; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 297 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 300 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 301 | SDKROOT = iphoneos; 302 | VALIDATE_PRODUCT = YES; 303 | }; 304 | name = Release; 305 | }; 306 | D0F2B1CA1780511E002053BA /* Debug */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 310 | GCC_PREFIX_HEADER = "Sample/Sample-Prefix.pch"; 311 | INFOPLIST_FILE = "Sample/Sample-Info.plist"; 312 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 313 | PRODUCT_NAME = "Google Now Style Card View Sample"; 314 | WRAPPER_EXTENSION = app; 315 | }; 316 | name = Debug; 317 | }; 318 | D0F2B1CB1780511E002053BA /* Release */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 322 | GCC_PREFIX_HEADER = "Sample/Sample-Prefix.pch"; 323 | INFOPLIST_FILE = "Sample/Sample-Info.plist"; 324 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 325 | PRODUCT_NAME = "Google Now Style Card View Sample"; 326 | WRAPPER_EXTENSION = app; 327 | }; 328 | name = Release; 329 | }; 330 | /* End XCBuildConfiguration section */ 331 | 332 | /* Begin XCConfigurationList section */ 333 | D0F2B1A11780511E002053BA /* Build configuration list for PBXProject "Sample" */ = { 334 | isa = XCConfigurationList; 335 | buildConfigurations = ( 336 | D0F2B1C71780511E002053BA /* Debug */, 337 | D0F2B1C81780511E002053BA /* Release */, 338 | ); 339 | defaultConfigurationIsVisible = 0; 340 | defaultConfigurationName = Release; 341 | }; 342 | D0F2B1C91780511E002053BA /* Build configuration list for PBXNativeTarget "Sample" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | D0F2B1CA1780511E002053BA /* Debug */, 346 | D0F2B1CB1780511E002053BA /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | /* End XCConfigurationList section */ 352 | }; 353 | rootObject = D0F2B19E1780511E002053BA /* Project object */; 354 | } 355 | -------------------------------------------------------------------------------- /Card View/UIColor+Expanded.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Expanded.m 3 | // Additions 4 | // 5 | // Created by Erica Sadun, http://ericasadun.com 6 | // iPhone Developer's Cookbook, 3.0 Edition 7 | // BSD License, Use at your own risk 8 | 9 | #import "UIColor+Expanded.h" 10 | 11 | /* 12 | Thanks to Poltras, Millenomi, Eridius, Nownot, WhatAHam, jberry, 13 | and everyone else who helped out but whose name is inadvertantly omitted 14 | */ 15 | 16 | /* 17 | Current outstanding request list: 18 | - PolarBearFarm - color descriptions ([UIColor warmGrayWithHintOfBlueTouchOfRedAndSplashOfYellowColor]) 19 | - Crayola color set 20 | - Eridius - UIColor needs a method that takes 2 colors and gives a third complementary one 21 | - Consider UIMutableColor that can be adjusted (brighter, cooler, warmer, thicker-alpha, etc) 22 | */ 23 | 24 | /* 25 | FOR REFERENCE: Color Space Models: enum CGColorSpaceModel { 26 | kCGColorSpaceModelUnknown = -1, 27 | kCGColorSpaceModelMonochrome, 28 | kCGColorSpaceModelRGB, 29 | kCGColorSpaceModelCMYK, 30 | kCGColorSpaceModelLab, 31 | kCGColorSpaceModelDeviceN, 32 | kCGColorSpaceModelIndexed, 33 | kCGColorSpaceModelPattern 34 | }; 35 | */ 36 | 37 | // Static cache of looked up color names. Used with +colorWithName: 38 | static NSMutableDictionary *colorNameCache = nil; 39 | 40 | #if SUPPORTS_UNDOCUMENTED_API 41 | // UIColor_Undocumented 42 | // Undocumented methods of UIColor 43 | @interface UIColor (UIColor_Undocumented) 44 | - (NSString *)styleString; 45 | @end 46 | #endif // SUPPORTS_UNDOCUMENTED_API 47 | 48 | @interface UIColor (UIColor_Expanded_Support) 49 | + (UIColor *) searchForColorByName:(NSString *)cssColorName; 50 | @end 51 | 52 | #pragma mark - 53 | 54 | @implementation UIColor (UIColor_Expanded) 55 | 56 | - (CGColorSpaceModel)colorSpaceModel { 57 | return CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor)); 58 | } 59 | 60 | - (NSString *)colorSpaceString { 61 | switch (self.colorSpaceModel) { 62 | case kCGColorSpaceModelUnknown: 63 | return @"kCGColorSpaceModelUnknown"; 64 | 65 | case kCGColorSpaceModelMonochrome: 66 | return @"kCGColorSpaceModelMonochrome"; 67 | 68 | case kCGColorSpaceModelRGB: 69 | return @"kCGColorSpaceModelRGB"; 70 | 71 | case kCGColorSpaceModelCMYK: 72 | return @"kCGColorSpaceModelCMYK"; 73 | 74 | case kCGColorSpaceModelLab: 75 | return @"kCGColorSpaceModelLab"; 76 | 77 | case kCGColorSpaceModelDeviceN: 78 | return @"kCGColorSpaceModelDeviceN"; 79 | 80 | case kCGColorSpaceModelIndexed: 81 | return @"kCGColorSpaceModelIndexed"; 82 | 83 | case kCGColorSpaceModelPattern: 84 | return @"kCGColorSpaceModelPattern"; 85 | 86 | default: 87 | return @"Not a valid color space"; 88 | } 89 | } 90 | 91 | - (BOOL)canProvideRGBComponents { 92 | switch (self.colorSpaceModel) { 93 | case kCGColorSpaceModelRGB: 94 | case kCGColorSpaceModelMonochrome: 95 | return YES; 96 | 97 | default: 98 | return NO; 99 | } 100 | } 101 | 102 | - (NSArray *)arrayFromRGBAComponents { 103 | NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -arrayFromRGBAComponents"); 104 | 105 | CGFloat r, g, b, a; 106 | 107 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 108 | 109 | return @[@(r), @(g), @(b), @(a)]; 110 | } 111 | 112 | - (BOOL)red:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha { 113 | const CGFloat *components = CGColorGetComponents(self.CGColor); 114 | 115 | CGFloat r, g, b, a; 116 | 117 | switch (self.colorSpaceModel) { 118 | case kCGColorSpaceModelMonochrome: 119 | r = g = b = components[0]; 120 | a = components[1]; 121 | break; 122 | 123 | case kCGColorSpaceModelRGB: 124 | r = components[0]; 125 | g = components[1]; 126 | b = components[2]; 127 | a = components[3]; 128 | break; 129 | 130 | default: // We don't know how to handle this model 131 | return NO; 132 | } 133 | 134 | if (red) *red = r; 135 | 136 | if (green) *green = g; 137 | 138 | if (blue) *blue = b; 139 | 140 | if (alpha) *alpha = a; 141 | 142 | return YES; 143 | } 144 | 145 | - (CGFloat)red { 146 | NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -red"); 147 | const CGFloat *c = CGColorGetComponents(self.CGColor); 148 | return c[0]; 149 | } 150 | 151 | - (CGFloat)green { 152 | NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -green"); 153 | const CGFloat *c = CGColorGetComponents(self.CGColor); 154 | 155 | if (self.colorSpaceModel == kCGColorSpaceModelMonochrome) return c[0]; 156 | 157 | return c[1]; 158 | } 159 | 160 | - (CGFloat)blue { 161 | NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -blue"); 162 | const CGFloat *c = CGColorGetComponents(self.CGColor); 163 | 164 | if (self.colorSpaceModel == kCGColorSpaceModelMonochrome) return c[0]; 165 | 166 | return c[2]; 167 | } 168 | 169 | - (CGFloat)white { 170 | NSAssert(self.colorSpaceModel == kCGColorSpaceModelMonochrome, @"Must be a Monochrome color to use -white"); 171 | const CGFloat *c = CGColorGetComponents(self.CGColor); 172 | return c[0]; 173 | } 174 | 175 | - (CGFloat)alpha { 176 | return CGColorGetAlpha(self.CGColor); 177 | } 178 | 179 | - (UInt32)rgbHex { 180 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use rgbHex"); 181 | 182 | CGFloat r, g, b, a; 183 | 184 | if (![self red:&r green:&g blue:&b alpha:&a]) return 0; 185 | 186 | r = MIN(MAX(self.red, 0.0f), 1.0f); 187 | g = MIN(MAX(self.green, 0.0f), 1.0f); 188 | b = MIN(MAX(self.blue, 0.0f), 1.0f); 189 | 190 | return (((int)roundf(r * 255)) << 16) 191 | | (((int)roundf(g * 255)) << 8) 192 | | (((int)roundf(b * 255))); 193 | } 194 | 195 | #pragma mark Arithmetic operations 196 | 197 | - (UIColor *)colorByLuminanceMapping { 198 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 199 | 200 | CGFloat r, g, b, a; 201 | 202 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 203 | 204 | // http://en.wikipedia.org/wiki/Luma_(video) 205 | // Y = 0.2126 R + 0.7152 G + 0.0722 B 206 | return [UIColor colorWithWhite:r * 0.2126f + g * 0.7152f + b * 0.0722f 207 | alpha:a]; 208 | } 209 | 210 | - (UIColor *)colorByMultiplyingByRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha { 211 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 212 | 213 | CGFloat r, g, b, a; 214 | 215 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 216 | 217 | return [UIColor colorWithRed:MAX(0.0, MIN(1.0, r * red)) 218 | green:MAX(0.0, MIN(1.0, g * green)) 219 | blue:MAX(0.0, MIN(1.0, b * blue)) 220 | alpha:MAX(0.0, MIN(1.0, a * alpha))]; 221 | } 222 | 223 | - (UIColor *)colorByAddingRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha { 224 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 225 | 226 | CGFloat r, g, b, a; 227 | 228 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 229 | 230 | return [UIColor colorWithRed:MAX(0.0, MIN(1.0, r + red)) 231 | green:MAX(0.0, MIN(1.0, g + green)) 232 | blue:MAX(0.0, MIN(1.0, b + blue)) 233 | alpha:MAX(0.0, MIN(1.0, a + alpha))]; 234 | } 235 | 236 | - (UIColor *)colorByLighteningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha { 237 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 238 | 239 | CGFloat r, g, b, a; 240 | 241 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 242 | 243 | return [UIColor colorWithRed:MAX(r, red) 244 | green:MAX(g, green) 245 | blue:MAX(b, blue) 246 | alpha:MAX(a, alpha)]; 247 | } 248 | 249 | - (UIColor *)colorByDarkeningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha { 250 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 251 | 252 | CGFloat r, g, b, a; 253 | 254 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 255 | 256 | return [UIColor colorWithRed:MIN(r, red) 257 | green:MIN(g, green) 258 | blue:MIN(b, blue) 259 | alpha:MIN(a, alpha)]; 260 | } 261 | 262 | - (UIColor *)colorByMultiplyingBy:(CGFloat)f { 263 | return [self colorByMultiplyingByRed:f green:f blue:f alpha:1.0f]; 264 | } 265 | 266 | - (UIColor *)colorByAdding:(CGFloat)f { 267 | return [self colorByMultiplyingByRed:f green:f blue:f alpha:0.0f]; 268 | } 269 | 270 | - (UIColor *)colorByLighteningTo:(CGFloat)f { 271 | return [self colorByLighteningToRed:f green:f blue:f alpha:0.0f]; 272 | } 273 | 274 | - (UIColor *)colorByDarkeningTo:(CGFloat)f { 275 | return [self colorByDarkeningToRed:f green:f blue:f alpha:1.0f]; 276 | } 277 | 278 | - (UIColor *)colorByMultiplyingByColor:(UIColor *)color { 279 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 280 | 281 | CGFloat r, g, b, a; 282 | 283 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 284 | 285 | return [self colorByMultiplyingByRed:r green:g blue:b alpha:1.0f]; 286 | } 287 | 288 | - (UIColor *)colorByAddingColor:(UIColor *)color { 289 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 290 | 291 | CGFloat r, g, b, a; 292 | 293 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 294 | 295 | return [self colorByAddingRed:r green:g blue:b alpha:0.0f]; 296 | } 297 | 298 | - (UIColor *)colorByLighteningToColor:(UIColor *)color { 299 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 300 | 301 | CGFloat r, g, b, a; 302 | 303 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 304 | 305 | return [self colorByLighteningToRed:r green:g blue:b alpha:0.0f]; 306 | } 307 | 308 | - (UIColor *)colorByDarkeningToColor:(UIColor *)color { 309 | NSAssert(self.canProvideRGBComponents, @"Must be a RGB color to use arithmatic operations"); 310 | 311 | CGFloat r, g, b, a; 312 | 313 | if (![self red:&r green:&g blue:&b alpha:&a]) return nil; 314 | 315 | return [self colorByDarkeningToRed:r green:g blue:b alpha:1.0f]; 316 | } 317 | 318 | #pragma mark String utilities 319 | 320 | - (NSString *)stringFromColor { 321 | NSAssert(self.canProvideRGBComponents, @"Must be an RGB color to use -stringFromColor"); 322 | NSString *result; 323 | switch (self.colorSpaceModel) { 324 | case kCGColorSpaceModelRGB: 325 | result = [NSString stringWithFormat:@"{%0.3f, %0.3f, %0.3f, %0.3f}", self.red, self.green, self.blue, self.alpha]; 326 | break; 327 | 328 | case kCGColorSpaceModelMonochrome: 329 | result = [NSString stringWithFormat:@"{%0.3f, %0.3f}", self.white, self.alpha]; 330 | break; 331 | 332 | default: 333 | result = nil; 334 | } 335 | return result; 336 | } 337 | 338 | - (NSString *)hexStringFromColor { 339 | return [NSString stringWithFormat:@"%0.6X", (unsigned int)self.rgbHex]; 340 | } 341 | 342 | + (UIColor *)colorWithString:(NSString *)stringToConvert { 343 | NSScanner *scanner = [NSScanner scannerWithString:stringToConvert]; 344 | 345 | if (![scanner scanString:@"{" intoString:NULL]) return nil; 346 | 347 | const NSUInteger kMaxComponents = 4; 348 | CGFloat c[kMaxComponents]; 349 | NSUInteger i = 0; 350 | 351 | if (![scanner scanFloat:&c[i++]]) return nil; 352 | 353 | while (1) { 354 | if ([scanner scanString:@"}" intoString:NULL]) break; 355 | 356 | if (i >= kMaxComponents) return nil; 357 | 358 | if ([scanner scanString:@"," intoString:NULL]) { 359 | if (![scanner scanFloat:&c[i++]]) return nil; 360 | } else { 361 | // either we're at the end of there's an unexpected character here 362 | // both cases are error conditions 363 | return nil; 364 | } 365 | } 366 | 367 | if (![scanner isAtEnd]) return nil; 368 | 369 | UIColor *color; 370 | switch (i) { 371 | case 2: // monochrome 372 | color = [UIColor colorWithWhite:c[0] alpha:c[1]]; 373 | break; 374 | 375 | case 4: // RGB 376 | color = [UIColor colorWithRed:c[0] green:c[1] blue:c[2] alpha:c[3]]; 377 | break; 378 | 379 | default: 380 | color = nil; 381 | } 382 | return color; 383 | } 384 | 385 | #pragma mark Class methods 386 | 387 | + (UIColor *)randomColor { 388 | return [UIColor colorWithRed:arc4random() % 255 / 255.0f 389 | green:arc4random() % 255 / 255.0f 390 | blue:arc4random() % 255 / 255.0f 391 | alpha:1.0f]; 392 | } 393 | 394 | + (UIColor *)colorWithRGBHex:(UInt32)hex { 395 | int r = (hex >> 16) & 0xFF; 396 | int g = (hex >> 8) & 0xFF; 397 | int b = (hex) & 0xFF; 398 | 399 | return [UIColor colorWithRed:r / 255.0f 400 | green:g / 255.0f 401 | blue:b / 255.0f 402 | alpha:1.0f]; 403 | } 404 | 405 | + (UIColor *)colorWithARGBHex:(UInt32)hex { 406 | int b = hex & 0x000000FF; 407 | int g = ((hex & 0x0000FF00) >> 8); 408 | int r = ((hex & 0x00FF0000) >> 16); 409 | int a = ((hex & 0xFF000000) >> 24); 410 | 411 | return [UIColor colorWithRed:r / 255.0f 412 | green:g / 255.0f 413 | blue:b / 255.0f 414 | alpha:a / 255.f]; 415 | } 416 | 417 | // Returns a UIColor by scanning the string for a hex number and passing that to +[UIColor colorWithRGBHex:] 418 | // Skips any leading whitespace and ignores any trailing characters 419 | + (UIColor *)colorWithRGBHexString:(NSString *)stringToConvert { 420 | NSScanner *scanner = [NSScanner scannerWithString:stringToConvert]; 421 | unsigned hexNum; 422 | 423 | if (![scanner scanHexInt:&hexNum]) return nil; 424 | 425 | return [UIColor colorWithRGBHex:hexNum]; 426 | } 427 | 428 | // Returns a UIColor by scanning the string for a hex number and passing that to +[UIColor colorWithARGBHex:] 429 | // Skips any leading whitespace and ignores any trailing characters 430 | + (UIColor *)colorWithARGBHexString:(NSString *)stringToConvert { 431 | NSScanner *scanner = [NSScanner scannerWithString:stringToConvert]; 432 | unsigned hexNum; 433 | 434 | if (![scanner scanHexInt:&hexNum]) return nil; 435 | 436 | return [UIColor colorWithARGBHex:hexNum]; 437 | } 438 | 439 | // Lookup a color using css 3/svg color name 440 | + (UIColor *)colorWithName:(NSString *)cssColorName { 441 | UIColor *color; 442 | 443 | @synchronized(colorNameCache) { 444 | // Look for the color in the cache 445 | color = colorNameCache[cssColorName]; 446 | 447 | if ((id)color == [NSNull null]) { 448 | // If it wasn't there previously, it's still not there now 449 | color = nil; 450 | } else if (!color) { 451 | // Color not in cache, so search for it now 452 | color = [self searchForColorByName:cssColorName]; 453 | 454 | // Set the value in cache, storing NSNull on failure 455 | colorNameCache[cssColorName] = (color ? : (id)[NSNull null]); 456 | } 457 | } 458 | 459 | return color; 460 | } 461 | 462 | #pragma mark UIColor_Expanded initialization 463 | 464 | + (void)load { 465 | colorNameCache = [[NSMutableDictionary alloc] init]; 466 | } 467 | 468 | @end 469 | 470 | #pragma mark - 471 | 472 | #if SUPPORTS_UNDOCUMENTED_API 473 | @implementation UIColor (UIColor_Undocumented_Expanded) 474 | - (NSString *)fetchStyleString { 475 | return [self styleString]; 476 | } 477 | 478 | // Convert a color into RGB Color space, courtesy of Poltras 479 | // via http://ofcodeandmen.poltras.com/2009/01/22/convert-a-cgcolorref-to-another-cgcolorspaceref/ 480 | // 481 | - (UIColor *)rgbColor { 482 | // Call to undocumented method "styleString". 483 | NSString *style = [self styleString]; 484 | NSScanner *scanner = [NSScanner scannerWithString:style]; 485 | CGFloat red, green, blue; 486 | 487 | if (![scanner scanString:@"rgb(" intoString:NULL]) return nil; 488 | 489 | if (![scanner scanFloat:&red]) return nil; 490 | 491 | if (![scanner scanString:@"," intoString:NULL]) return nil; 492 | 493 | if (![scanner scanFloat:&green]) return nil; 494 | 495 | if (![scanner scanString:@"," intoString:NULL]) return nil; 496 | 497 | if (![scanner scanFloat:&blue]) return nil; 498 | 499 | if (![scanner scanString:@")" intoString:NULL]) return nil; 500 | 501 | if (![scanner isAtEnd]) return nil; 502 | 503 | return [UIColor colorWithRed:red green:green blue:blue alpha:self.alpha]; 504 | } 505 | 506 | @end 507 | #endif // SUPPORTS_UNDOCUMENTED_API 508 | 509 | @implementation UIColor (UIColor_Expanded_Support) 510 | /* 511 | * Database of color names and hex rgb values, derived 512 | * from the css 3 color spec: 513 | * http://www.w3.org/TR/css3-color/ 514 | * 515 | * We think this is a very compact way of storing 516 | * this information, and relatively cheap to lookup. 517 | * 518 | * Note that we search for color names starting with ',' 519 | * and terminated by '#', so that we don't get false matches. 520 | * For this reason, the database begins with ','. 521 | */ 522 | static const char *colorNameDB = "," 523 | "aliceblue#f0f8ff,antiquewhite#faebd7,aqua#00ffff,aquamarine#7fffd4,azure#f0ffff," 524 | "beige#f5f5dc,bisque#ffe4c4,black#000000,blanchedalmond#ffebcd,blue#0000ff," 525 | "blueviolet#8a2be2,brown#a52a2a,burlywood#deb887,cadetblue#5f9ea0,chartreuse#7fff00," 526 | "chocolate#d2691e,coral#ff7f50,cornflowerblue#6495ed,cornsilk#fff8dc,crimson#dc143c," 527 | "cyan#00ffff,darkblue#00008b,darkcyan#008b8b,darkgoldenrod#b8860b,darkgray#a9a9a9," 528 | "darkgreen#006400,darkgrey#a9a9a9,darkkhaki#bdb76b,darkmagenta#8b008b," 529 | "darkolivegreen#556b2f,darkorange#ff8c00,darkorchid#9932cc,darkred#8b0000," 530 | "darksalmon#e9967a,darkseagreen#8fbc8f,darkslateblue#483d8b,darkslategray#2f4f4f," 531 | "darkslategrey#2f4f4f,darkturquoise#00ced1,darkviolet#9400d3,deeppink#ff1493," 532 | "deepskyblue#00bfff,dimgray#696969,dimgrey#696969,dodgerblue#1e90ff," 533 | "firebrick#b22222,floralwhite#fffaf0,forestgreen#228b22,fuchsia#ff00ff," 534 | "gainsboro#dcdcdc,ghostwhite#f8f8ff,gold#ffd700,goldenrod#daa520,gray#808080," 535 | "green#008000,greenyellow#adff2f,grey#808080,honeydew#f0fff0,hotpink#ff69b4," 536 | "indianred#cd5c5c,indigo#4b0082,ivory#fffff0,khaki#f0e68c,lavender#e6e6fa," 537 | "lavenderblush#fff0f5,lawngreen#7cfc00,lemonchiffon#fffacd,lightblue#add8e6," 538 | "lightcoral#f08080,lightcyan#e0ffff,lightgoldenrodyellow#fafad2,lightgray#d3d3d3," 539 | "lightgreen#90ee90,lightgrey#d3d3d3,lightpink#ffb6c1,lightsalmon#ffa07a," 540 | "lightseagreen#20b2aa,lightskyblue#87cefa,lightslategray#778899," 541 | "lightslategrey#778899,lightsteelblue#b0c4de,lightyellow#ffffe0,lime#00ff00," 542 | "limegreen#32cd32,linen#faf0e6,magenta#ff00ff,maroon#800000,mediumaquamarine#66cdaa," 543 | "mediumblue#0000cd,mediumorchid#ba55d3,mediumpurple#9370db,mediumseagreen#3cb371," 544 | "mediumslateblue#7b68ee,mediumspringgreen#00fa9a,mediumturquoise#48d1cc," 545 | "mediumvioletred#c71585,midnightblue#191970,mintcream#f5fffa,mistyrose#ffe4e1," 546 | "moccasin#ffe4b5,navajowhite#ffdead,navy#000080,oldlace#fdf5e6,olive#808000," 547 | "olivedrab#6b8e23,orange#ffa500,orangered#ff4500,orchid#da70d6,palegoldenrod#eee8aa," 548 | "palegreen#98fb98,paleturquoise#afeeee,palevioletred#db7093,papayawhip#ffefd5," 549 | "peachpuff#ffdab9,peru#cd853f,pink#ffc0cb,plum#dda0dd,powderblue#b0e0e6," 550 | "purple#800080,red#ff0000,rosybrown#bc8f8f,royalblue#4169e1,saddlebrown#8b4513," 551 | "salmon#fa8072,sandybrown#f4a460,seagreen#2e8b57,seashell#fff5ee,sienna#a0522d," 552 | "silver#c0c0c0,skyblue#87ceeb,slateblue#6a5acd,slategray#708090,slategrey#708090," 553 | "snow#fffafa,springgreen#00ff7f,steelblue#4682b4,tan#d2b48c,teal#008080," 554 | "thistle#d8bfd8,tomato#ff6347,turquoise#40e0d0,violet#ee82ee,wheat#f5deb3," 555 | "white#ffffff,whitesmoke#f5f5f5,yellow#ffff00,yellowgreen#9acd32"; 556 | 557 | + (UIColor *)searchForColorByName:(NSString *)cssColorName { 558 | UIColor *result = nil; 559 | 560 | // Compile the string we'll use to search against the database 561 | // We search for ",#" to avoid false matches 562 | const char *searchString = [[NSString stringWithFormat:@",%@#", cssColorName] UTF8String]; 563 | 564 | // Search for the color name 565 | const char *found = strstr(colorNameDB, searchString); 566 | 567 | // If found, step past the search string and grab the hex representation 568 | if (found) { 569 | const char *after = found + strlen(searchString); 570 | int hex; 571 | 572 | if (sscanf(after, "%x", &hex) == 1) { 573 | result = [self colorWithRGBHex:hex]; 574 | } 575 | } 576 | 577 | return result; 578 | } 579 | 580 | @end 581 | -------------------------------------------------------------------------------- /Card View/UIView+Folding.m: -------------------------------------------------------------------------------- 1 | #import "UIView+Folding.h" 2 | 3 | static NSUInteger _KBTransitionState = KBFoldingTransitionStateIdle; 4 | 5 | KeyframeParametrizedBlock kbOpenFunction = ^double (NSTimeInterval time) { 6 | return sin(time * M_PI_2); 7 | }; 8 | 9 | KeyframeParametrizedBlock kbCloseFunction = ^double (NSTimeInterval time) { 10 | return -cos(time * M_PI_2) + 1.0f; 11 | }; 12 | 13 | @implementation CAKeyframeAnimation (Parametrized) 14 | // 15 | // Private Interface 16 | // 17 | + (id)parametrizedAnimationWithKeyPath:(NSString *)path 18 | function:(KeyframeParametrizedBlock)function 19 | fromValue:(CGFloat)fromValue 20 | toValue:(CGFloat)toValue { 21 | CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:path]; 22 | NSUInteger steps = 100; 23 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:steps]; 24 | double time = 0.0f; 25 | double timeStep = 1.0 / (double)(steps - 1); 26 | 27 | for (NSUInteger i = 0; i < steps; ++i) { 28 | double value = fromValue + (function(time) * (toValue - fromValue)); 29 | [values addObject:@(value)]; 30 | time += timeStep; 31 | } 32 | 33 | animation.calculationMode = kCAAnimationLinear; 34 | [animation setValues:values]; 35 | return animation; 36 | } 37 | 38 | @end 39 | 40 | @interface UIView (FoldingPrivate) 41 | 42 | - (BOOL)validateDuration:(NSTimeInterval)duration direction:(NSUInteger)direction folds:(NSUInteger)folds; 43 | 44 | @end 45 | 46 | @implementation UIView (Folding) 47 | 48 | - (NSUInteger)state { 49 | return _KBTransitionState; 50 | } 51 | 52 | + (CATransformLayer *)transformLayerfromImage:(UIImage *)image 53 | frame:(CGRect)frame 54 | duration:(NSTimeInterval)duration 55 | anchorPoint:(CGPoint)anchorPoint 56 | startAngle:(CGFloat)startAngle 57 | endAngle:(CGFloat)endAngle { 58 | CATransformLayer *jointLayer = [CATransformLayer layer]; 59 | 60 | jointLayer.anchorPoint = anchorPoint; 61 | CALayer *imageLayer = [CALayer layer]; 62 | CAGradientLayer *shadowLayer = [CAGradientLayer layer]; 63 | double shadowAniOpacity = 0.0f; 64 | 65 | if (anchorPoint.y == 0.5f) { 66 | CGFloat layerWidth = 0.0f; 67 | 68 | if (anchorPoint.x == 0.0f) { 69 | layerWidth = image.size.width - frame.origin.x; 70 | jointLayer.frame = CGRectMake(0.0f, 0.0f, layerWidth, frame.size.height); 71 | 72 | if (frame.origin.x) { 73 | jointLayer.position = CGPointMake(frame.size.width, frame.size.height / 2.0f); 74 | } else { 75 | jointLayer.position = CGPointMake(0.0f, frame.size.height / 2.0f); 76 | } 77 | } else { 78 | layerWidth = frame.origin.x + frame.size.width; 79 | jointLayer.frame = CGRectMake(0.0f, 0.0f, layerWidth, frame.size.height); 80 | jointLayer.position = CGPointMake(layerWidth, frame.size.height / 2.0f); 81 | } 82 | 83 | // Map the image onto the transform layer 84 | imageLayer.frame = CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height); 85 | imageLayer.anchorPoint = anchorPoint; 86 | imageLayer.position = CGPointMake(layerWidth * anchorPoint.x, frame.size.height / 2.0f); 87 | [jointLayer addSublayer:imageLayer]; 88 | 89 | CGImageRef imageCrop = CGImageCreateWithImageInRect(image.CGImage, frame); 90 | imageLayer.contents = (id)imageCrop; 91 | CFRelease(imageCrop); 92 | imageLayer.backgroundColor = [UIColor clearColor].CGColor; 93 | 94 | // Add drop shadow 95 | NSInteger index = frame.origin.x / frame.size.width; 96 | shadowLayer.frame = imageLayer.bounds; 97 | shadowLayer.backgroundColor = [UIColor darkGrayColor].CGColor; 98 | shadowLayer.opacity = 0.0f; 99 | shadowLayer.colors = @[(id)[UIColor blackColor].CGColor, 100 | (id)[UIColor clearColor].CGColor]; 101 | 102 | if (index % 2 != 0.0f) { 103 | shadowLayer.startPoint = CGPointMake(0.0f, 0.5f); 104 | shadowLayer.endPoint = CGPointMake(1.0f, 0.5f); 105 | shadowAniOpacity = (anchorPoint.x != 0.0f) ? 0.24f : 0.32f; 106 | } else { 107 | shadowLayer.startPoint = CGPointMake(1.0f, 0.5f); 108 | shadowLayer.endPoint = CGPointMake(0.0f, 0.5f); 109 | shadowAniOpacity = (anchorPoint.x != 0.0f) ? 0.32f : 0.24f; 110 | } 111 | 112 | [imageLayer addSublayer:shadowLayer]; 113 | } else { 114 | CGFloat layerHeight; 115 | 116 | if (anchorPoint.y == 0.0f) { 117 | layerHeight = image.size.height - frame.origin.y; 118 | jointLayer.frame = CGRectMake(0.0f, 0.0f, frame.size.width, layerHeight); 119 | 120 | if (frame.origin.y) { 121 | jointLayer.position = CGPointMake(frame.size.width / 2.0f, frame.size.height); 122 | } else { 123 | jointLayer.position = CGPointMake(frame.size.width / 2.0f, 0.0f); 124 | } 125 | } else { 126 | layerHeight = frame.origin.y + frame.size.height; 127 | jointLayer.frame = CGRectMake(0.0f, 0.0f, frame.size.width, layerHeight); 128 | jointLayer.position = CGPointMake(frame.size.width / 2.0f, layerHeight); 129 | } 130 | 131 | // Map the image onto the transform layer 132 | imageLayer.frame = CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height); 133 | imageLayer.anchorPoint = anchorPoint; 134 | imageLayer.position = CGPointMake(frame.size.width / 2.0f, layerHeight * anchorPoint.y); 135 | [jointLayer addSublayer:imageLayer]; 136 | 137 | CGImageRef imageCrop = CGImageCreateWithImageInRect(image.CGImage, frame); 138 | imageLayer.contents = (id)imageCrop; 139 | CFRelease(imageCrop); 140 | imageLayer.backgroundColor = [UIColor clearColor].CGColor; 141 | 142 | // Add a drop-shadow layer 143 | NSInteger index = frame.origin.y / frame.size.height; 144 | shadowLayer.frame = imageLayer.bounds; 145 | shadowLayer.backgroundColor = [UIColor darkGrayColor].CGColor; 146 | shadowLayer.opacity = 0.0f; 147 | shadowLayer.colors = @[(id)[UIColor blackColor].CGColor, 148 | (id)[UIColor clearColor].CGColor]; 149 | 150 | if (index % 2 != 0.0f) { 151 | shadowLayer.startPoint = CGPointMake(0.05f, 0.0f); 152 | shadowLayer.endPoint = CGPointMake(0.5f, 1.0f); 153 | shadowAniOpacity = (anchorPoint.x != 0.0f) ? 0.24f : 0.32f; 154 | } else { 155 | shadowLayer.startPoint = CGPointMake(0.5f, 1.0f); 156 | shadowLayer.endPoint = CGPointMake(0.5f, 0.0f); 157 | shadowAniOpacity = (anchorPoint.x != 0.0f) ? 0.32f : 0.24f; 158 | } 159 | 160 | [imageLayer addSublayer:shadowLayer]; 161 | } 162 | 163 | // Configure the open/close animation 164 | CABasicAnimation *animation = (anchorPoint.y == 0.5) ? 165 | [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"] : 166 | [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"]; 167 | [animation setDuration:duration]; 168 | [animation setFromValue:[NSNumber numberWithDouble:startAngle]]; 169 | [animation setToValue:[NSNumber numberWithDouble:endAngle]]; 170 | [animation setRemovedOnCompletion:NO]; 171 | [jointLayer addAnimation:animation forKey:@"jointAnimation"]; 172 | 173 | // Configure the shadow opacity 174 | animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 175 | [animation setDuration:duration]; 176 | [animation setFromValue:@((startAngle != 0.0f) ? shadowAniOpacity : 0.0f)]; 177 | [animation setToValue:@((startAngle != 0.0f) ? 0.0f : shadowAniOpacity)]; 178 | [shadowLayer addAnimation:animation forKey:nil]; 179 | 180 | return jointLayer; 181 | } 182 | 183 | #pragma mark - 184 | #pragma mark Show Methods 185 | 186 | // 187 | // SHOW METHODS 188 | // 189 | - (void)showFoldingView:(UIView *)view 190 | backgroundColor:(UIColor *)backgroundColor 191 | folds:(NSUInteger)folds 192 | direction:(NSUInteger)direction 193 | duration:(NSTimeInterval)duration 194 | onCompletion:(void (^)(BOOL finished))onCompletion { 195 | // 196 | // Guard the Method Invocation 197 | // 198 | #ifdef kbFoldingViewUseBoundsChecking 199 | 200 | if (![self validateDuration:duration direction:direction folds:folds]) { 201 | return; 202 | } 203 | 204 | #endif 205 | 206 | if (self.state != KBFoldingTransitionStateIdle) { 207 | return; 208 | } 209 | 210 | _KBTransitionState = KBFoldingTransitionStateUpdate; 211 | 212 | // 213 | // Configure the target subview 214 | // 215 | if ([view superview] != nil) { 216 | [view removeFromSuperview]; 217 | } 218 | 219 | [[self superview] insertSubview:view belowSubview:self]; 220 | 221 | // 222 | // Configure the target frame 223 | // 224 | CGPoint anchorPoint = CGPointZero; 225 | switch (direction) { 226 | case KBFoldingViewDirectionFromRight: 227 | anchorPoint = CGPointMake(1.0f, 0.5f); 228 | break; 229 | 230 | case KBFoldingViewDirectionFromLeft: 231 | anchorPoint = CGPointMake(0.0f, 0.5f); 232 | break; 233 | 234 | case KBFoldingViewDirectionFromTop: 235 | anchorPoint = CGPointMake(0.5f, 0.0f); 236 | break; 237 | 238 | case KBFoldingViewDirectionFromBottom: 239 | anchorPoint = CGPointMake(0.5f, 1.0f); 240 | break; 241 | } 242 | 243 | // 244 | // Grab the snapshot of the image 245 | // 246 | UIGraphicsBeginImageContext(view.frame.size); 247 | [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 248 | UIImage *viewSnapShot = UIGraphicsGetImageFromCurrentImageContext(); 249 | UIGraphicsGetCurrentContext(); 250 | 251 | // 252 | // Set 3D Depth 253 | // 254 | CATransform3D transform = CATransform3DIdentity; 255 | transform.m34 = -1.0f / 800.0f; 256 | CALayer *foldingLayer = [CALayer layer]; 257 | foldingLayer.frame = view.bounds; 258 | foldingLayer.backgroundColor = backgroundColor.CGColor; 259 | foldingLayer.sublayerTransform = transform; 260 | [view.layer addSublayer:foldingLayer]; 261 | 262 | // 263 | // Set up rotating angle 264 | // 265 | double startAngle = 0.0f; 266 | CALayer *prevLayer = foldingLayer; 267 | CGFloat frameWidth = view.bounds.size.width; 268 | CGFloat frameHeight = view.bounds.size.height; 269 | CGFloat foldWidth = 0.0f; 270 | CGRect imageFrame = CGRectZero; 271 | switch (direction) { 272 | case KBFoldingViewDirectionFromRight: 273 | foldWidth = frameWidth / (folds * 2.0f); 274 | 275 | for (int b = 0; b < 2 * folds; ++b) { 276 | if (b == 0) { 277 | startAngle = -M_PI_2; 278 | } else if (b % 2) { 279 | startAngle = M_PI; 280 | } else { startAngle = -M_PI; } 281 | 282 | imageFrame = CGRectMake(frameWidth - (b + 1) * foldWidth, 0, foldWidth, frameHeight); 283 | 284 | CATransformLayer *transLayer = [UIView transformLayerfromImage:viewSnapShot 285 | frame:imageFrame 286 | duration:duration 287 | anchorPoint:anchorPoint 288 | startAngle:startAngle 289 | endAngle:0]; 290 | [prevLayer addSublayer:transLayer]; 291 | prevLayer = transLayer; 292 | } 293 | 294 | break; 295 | 296 | case KBFoldingViewDirectionFromLeft: 297 | foldWidth = frameWidth / (folds * 2.0f); 298 | 299 | for (int b = 0; b < 2 * folds; ++b) { 300 | if (b == 0) { 301 | startAngle = M_PI_2; 302 | } else if (b % 2) { 303 | startAngle = -M_PI; 304 | } else { startAngle = M_PI; } 305 | 306 | imageFrame = CGRectMake(b * foldWidth, 0, foldWidth, frameHeight); 307 | 308 | CATransformLayer *transLayer = [UIView transformLayerfromImage:viewSnapShot 309 | frame:imageFrame 310 | duration:duration 311 | anchorPoint:anchorPoint 312 | startAngle:startAngle 313 | endAngle:0]; 314 | [prevLayer addSublayer:transLayer]; 315 | prevLayer = transLayer; 316 | } 317 | 318 | break; 319 | 320 | case KBFoldingViewDirectionFromTop: 321 | foldWidth = frameHeight / (folds * 2.0f); 322 | 323 | for (int b = 0; b < 2 * folds; ++b) { 324 | if (b == 0) { 325 | startAngle = -M_PI_2; 326 | } else if (b % 2) { 327 | startAngle = M_PI; 328 | } else { startAngle = -M_PI; } 329 | 330 | imageFrame = CGRectMake(0, b * foldWidth, frameWidth, foldWidth); 331 | 332 | CATransformLayer *transLayer = [UIView transformLayerfromImage:viewSnapShot 333 | frame:imageFrame 334 | duration:duration 335 | anchorPoint:anchorPoint 336 | startAngle:startAngle 337 | endAngle:0]; 338 | [prevLayer addSublayer:transLayer]; 339 | prevLayer = transLayer; 340 | } 341 | 342 | break; 343 | 344 | case KBFoldingViewDirectionFromBottom: 345 | foldWidth = frameHeight / (folds * 2.0f); 346 | 347 | for (int b = 0; b < 2 * folds; ++b) { 348 | if (b == 0) { 349 | startAngle = M_PI_2; 350 | } else if (b % 2) { 351 | startAngle = -M_PI; 352 | } else { startAngle = M_PI; } 353 | 354 | imageFrame = CGRectMake(0, frameHeight - (b + 1) * foldWidth, frameWidth, foldWidth); 355 | 356 | CATransformLayer *transLayer = [UIView transformLayerfromImage:viewSnapShot 357 | frame:imageFrame 358 | duration:duration 359 | anchorPoint:anchorPoint 360 | startAngle:startAngle 361 | endAngle:0]; 362 | [prevLayer addSublayer:transLayer]; 363 | prevLayer = transLayer; 364 | } 365 | 366 | break; 367 | } 368 | 369 | // 370 | // Construct and Commit the Open Animation 371 | // 372 | [CATransaction begin]; 373 | [CATransaction setCompletionBlock: ^{ 374 | [foldingLayer removeFromSuperlayer]; 375 | 376 | // Reset the transition state 377 | _KBTransitionState = KBFoldingTransitionStateShowing; 378 | 379 | if (onCompletion) { 380 | onCompletion(YES); 381 | } 382 | }]; 383 | 384 | [CATransaction setValue:[NSNumber numberWithFloat:duration] forKey:kCATransactionAnimationDuration]; 385 | 386 | CAAnimation *openAnimation = nil; 387 | switch (direction) { 388 | case KBFoldingViewDirectionFromRight: 389 | case KBFoldingViewDirectionFromLeft: 390 | openAnimation = [CAKeyframeAnimation parametrizedAnimationWithKeyPath:@"position.x" 391 | function:kbOpenFunction 392 | fromValue:self.layer.position.x 393 | toValue:self.layer.position.x]; 394 | break; 395 | 396 | case KBFoldingViewDirectionFromTop: 397 | case KBFoldingViewDirectionFromBottom: 398 | openAnimation = [CAKeyframeAnimation parametrizedAnimationWithKeyPath:@"position.y" 399 | function:kbOpenFunction 400 | fromValue:self.layer.position.y 401 | toValue:self.layer.position.y]; 402 | break; 403 | } 404 | openAnimation.fillMode = kCAFillModeForwards; 405 | openAnimation.removedOnCompletion = NO; 406 | [self.layer addAnimation:openAnimation forKey:@"position"]; 407 | 408 | [CATransaction commit]; 409 | } 410 | 411 | #pragma mark - 412 | #pragma mark Hide Methods 413 | 414 | - (void)hideFoldingView:(UIView *)view 415 | backgroundColor:(UIColor *)backgroundColor 416 | folds:(NSUInteger)folds 417 | direction:(NSUInteger)direction 418 | duration:(NSTimeInterval)duration 419 | onCompletion:(void (^)(BOOL finished))onCompletion { 420 | // 421 | // Guard the Method Invocation 422 | // 423 | #ifdef kbFoldingViewUseBoundsChecking 424 | 425 | if (![self validateDuration:duration direction:direction folds:folds]) { 426 | return; 427 | } 428 | 429 | #endif 430 | 431 | if (self.state != KBFoldingTransitionStateShowing) { 432 | return; 433 | } 434 | 435 | _KBTransitionState = KBFoldingTransitionStateUpdate; 436 | 437 | // 438 | // Configure the Target Frame 439 | // 440 | CGPoint anchorPoint = CGPointZero; 441 | switch (direction) { 442 | case KBFoldingViewDirectionFromRight: 443 | anchorPoint = CGPointMake(1.0f, 0.5f); 444 | break; 445 | 446 | case KBFoldingViewDirectionFromLeft: 447 | anchorPoint = CGPointMake(0.0f, 0.5f); 448 | break; 449 | 450 | case KBFoldingViewDirectionFromTop: 451 | anchorPoint = CGPointMake(0.5f, 0.0f); 452 | break; 453 | 454 | case KBFoldingViewDirectionFromBottom: 455 | anchorPoint = CGPointMake(0.5f, 1.0f); 456 | break; 457 | } 458 | 459 | // 460 | // Capture a snapshot of the image 461 | // 462 | UIGraphicsBeginImageContext(view.frame.size); 463 | [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 464 | UIImage *viewSnapShot = UIGraphicsGetImageFromCurrentImageContext(); 465 | UIGraphicsEndImageContext(); 466 | 467 | // 468 | // Configure 3D Path 469 | // 470 | CATransform3D transform = CATransform3DIdentity; 471 | transform.m34 = -1.0 / 800.0f; 472 | CALayer *foldingLayer = [CALayer layer]; 473 | foldingLayer.frame = view.bounds; 474 | foldingLayer.backgroundColor = backgroundColor.CGColor; 475 | foldingLayer.sublayerTransform = transform; 476 | [view.layer addSublayer:foldingLayer]; 477 | 478 | // 479 | // Setup rotation angle 480 | // 481 | double endAngle = 0.0f; 482 | CGFloat foldWidth = 0.0f; 483 | CGFloat frameWidth = view.bounds.size.width; 484 | CGFloat frameHeight = view.bounds.size.height; 485 | CALayer *prevLayer = foldingLayer; 486 | CGRect imageFrame; 487 | switch (direction) { 488 | case KBFoldingViewDirectionFromRight: 489 | foldWidth = frameWidth / (folds * 2.0f); 490 | 491 | for (int b = 0; b < 2 * folds; ++b) { 492 | if (b == 0) { 493 | endAngle = -M_PI_2; 494 | } else if (b % 2) { 495 | endAngle = M_PI; 496 | } else { endAngle = -M_PI; } 497 | 498 | imageFrame = CGRectMake(frameWidth - (b + 1) * foldWidth, 0.0f, foldWidth, frameHeight); 499 | 500 | CATransformLayer *transLayer = [UIView transformLayerfromImage:viewSnapShot 501 | frame:imageFrame 502 | duration:duration 503 | anchorPoint:anchorPoint 504 | startAngle:0.0f 505 | endAngle:endAngle]; 506 | [prevLayer addSublayer:transLayer]; 507 | prevLayer = transLayer; 508 | } 509 | 510 | break; 511 | 512 | case KBFoldingViewDirectionFromLeft: 513 | foldWidth = frameWidth / (folds * 2.0f); 514 | 515 | for (int b = 0; b < 2 * folds; ++b) { 516 | if (b == 0) { 517 | endAngle = M_PI_2; 518 | } else if (b % 2) { 519 | endAngle = -M_PI; 520 | } else { endAngle = M_PI; } 521 | 522 | imageFrame = CGRectMake(b * foldWidth, 0.0f, foldWidth, frameHeight); 523 | 524 | CATransformLayer *transLayer = [UIView transformLayerfromImage:viewSnapShot 525 | frame:imageFrame 526 | duration:duration 527 | anchorPoint:anchorPoint 528 | startAngle:0.0f 529 | endAngle:endAngle]; 530 | [prevLayer addSublayer:transLayer]; 531 | prevLayer = transLayer; 532 | } 533 | 534 | break; 535 | 536 | case KBFoldingViewDirectionFromTop: 537 | foldWidth = frameHeight / (folds * 2.0f); 538 | 539 | for (int b = 0; b < 2 * folds; ++b) { 540 | if (b == 0) { 541 | endAngle = -M_PI_2; 542 | } else if (b % 2) { 543 | endAngle = M_PI; 544 | } else { endAngle = -M_PI; } 545 | 546 | imageFrame = CGRectMake(0.0f, b * foldWidth, frameWidth, foldWidth); 547 | 548 | CATransformLayer *transLayer = [UIView transformLayerfromImage:viewSnapShot 549 | frame:imageFrame 550 | duration:duration 551 | anchorPoint:anchorPoint 552 | startAngle:0.0f 553 | endAngle:endAngle]; 554 | [prevLayer addSublayer:transLayer]; 555 | prevLayer = transLayer; 556 | } 557 | 558 | break; 559 | 560 | case KBFoldingViewDirectionFromBottom: 561 | foldWidth = frameHeight / (folds * 2.0f); 562 | 563 | for (int b = 0; b < 2 * folds; ++b) { 564 | if (b == 0) { 565 | endAngle = M_PI_2; 566 | } else if (b % 2) { 567 | endAngle = -M_PI; 568 | } else { endAngle = M_PI; } 569 | 570 | imageFrame = CGRectMake(0.0f, frameHeight - (b + 1) * foldWidth, frameWidth, foldWidth); 571 | 572 | CATransformLayer *transLayer = [UIView transformLayerfromImage:viewSnapShot 573 | frame:imageFrame 574 | duration:duration 575 | anchorPoint:anchorPoint 576 | startAngle:0.0f 577 | endAngle:endAngle]; 578 | [prevLayer addSublayer:transLayer]; 579 | prevLayer = transLayer; 580 | } 581 | 582 | break; 583 | } 584 | 585 | // 586 | // Construct and Commit the Close Animation 587 | // 588 | [CATransaction begin]; 589 | [CATransaction setCompletionBlock: ^{ 590 | [foldingLayer removeFromSuperlayer]; 591 | _KBTransitionState = KBFoldingTransitionStateIdle; 592 | 593 | // Reset the transition state 594 | _KBTransitionState = KBFoldingTransitionStateIdle; 595 | 596 | if (onCompletion) { 597 | onCompletion(YES); 598 | } 599 | }]; 600 | 601 | [CATransaction setValue:[NSNumber numberWithFloat:duration] forKey:kCATransactionAnimationDuration]; 602 | 603 | CAAnimation *closeAnimation = nil; 604 | switch (direction) { 605 | case KBFoldingViewDirectionFromRight: 606 | case KBFoldingViewDirectionFromLeft: 607 | closeAnimation = [CAKeyframeAnimation parametrizedAnimationWithKeyPath:@"position.x" 608 | function:kbCloseFunction 609 | fromValue:self.layer.position.x 610 | toValue:self.layer.position.x]; 611 | break; 612 | 613 | case KBFoldingViewDirectionFromTop: 614 | case KBFoldingViewDirectionFromBottom: 615 | closeAnimation = [CAKeyframeAnimation parametrizedAnimationWithKeyPath:@"position.y" 616 | function:kbCloseFunction 617 | fromValue:self.layer.position.y 618 | toValue:self.layer.position.y]; 619 | break; 620 | } 621 | closeAnimation.fillMode = kCAFillModeForwards; 622 | closeAnimation.removedOnCompletion = NO; 623 | [self.layer addAnimation:closeAnimation forKey:@"position"]; 624 | [CATransaction commit]; 625 | } 626 | 627 | #pragma mark - 628 | #pragma mark Validation Method 629 | 630 | - (BOOL)validateDuration:(NSTimeInterval)duration direction:(NSUInteger)direction folds:(NSUInteger)folds; { 631 | if (!(direction == KBFoldingViewDirectionFromRight || 632 | direction == KBFoldingViewDirectionFromLeft || 633 | direction == KBFoldingViewDirectionFromTop || 634 | direction == KBFoldingViewDirectionFromBottom)) { 635 | NSLog(@"[KBFoldingView] -- Error -- Invalid direction: %d", direction); 636 | return NO; 637 | } 638 | 639 | if (folds < kbFoldingViewMinFolds || folds > kbFoldingViewMaxFolds) { 640 | NSLog(@"[KBFoldingView] -- Error -- Number of folds must be between %d and %d", kbFoldingViewMinFolds, kbFoldingViewMaxFolds); 641 | return NO; 642 | } 643 | 644 | if (duration < kbFoldingViewMinDuration || duration > kbFoldingViewMaxDuration) { 645 | NSLog(@"[KBFoldingView] -- Error -- Duration must be between %f and %f", kbFoldingViewMinDuration, kbFoldingViewMaxDuration); 646 | return NO; 647 | } 648 | 649 | return YES; 650 | } 651 | 652 | @end 653 | -------------------------------------------------------------------------------- /Card View/RSCardsView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSCardsView.m 3 | // Google Now Style Card View 4 | // 5 | // Created by R0CKSTAR on 5/21/13. 6 | // Copyright (c) 2013 P.D.Q. All rights reserved. 7 | // 8 | 9 | #import "RSCardsView.h" 10 | #import 11 | 12 | static CGFloat const kRSExchangeAnimationScaleZoomIn = 1.01; 13 | static CGFloat const kRSExchangeAnimationScaleZoomOut = 0.99; 14 | static CGFloat const kRSDropAnimationScaleZoomOut = 0.98; 15 | static NSTimeInterval const kRSAnimationDuration = 0.35; 16 | 17 | @interface RSCardsView () { 18 | BOOL _isReloading; 19 | BOOL _isInserting; 20 | 21 | NSMutableArray *_indexPaths; 22 | NSMutableArray *_insertQueue; 23 | } 24 | 25 | @end 26 | 27 | @implementation RSCardsView 28 | 29 | static const int kTagBase = NSIntegerMax / 10 * 10; 30 | static const int kSectionSpan = 100; 31 | 32 | @synthesize delegate = __delegate; 33 | @synthesize dataSource = __dataSource; 34 | 35 | #pragma mark - Private: getters 36 | 37 | - (NSMutableArray *)insertQueue { 38 | if (!_insertQueue) { 39 | _insertQueue = [[NSMutableArray alloc] init]; 40 | } 41 | 42 | return _insertQueue; 43 | } 44 | 45 | - (NSMutableArray *)indexPaths { 46 | if (!_indexPaths) { 47 | _indexPaths = [[NSMutableArray alloc] init]; 48 | } 49 | 50 | return _indexPaths; 51 | } 52 | 53 | #pragma mark - Private: insert queued card 54 | 55 | - (void)insertQueuedCard { 56 | if ([self insertQueue].count > 0) { 57 | [self insertCard:[self insertQueue][0]]; 58 | [[self insertQueue] removeObjectAtIndex:0]; 59 | } 60 | } 61 | 62 | #pragma mark - Private: card <-> index path 63 | 64 | - (int)rowForCard:(RSCardView *)card { 65 | return (card.tag - kTagBase) % kSectionSpan; 66 | } 67 | 68 | - (int)sectionForCard:(RSCardView *)card { 69 | return (card.tag - kTagBase) / kSectionSpan; 70 | } 71 | 72 | - (NSIndexPath *)indexPathForCard:(RSCardView *)card { 73 | return [NSIndexPath indexPathForRow:[self rowForCard:card] inSection:[self sectionForCard:card]]; 74 | } 75 | 76 | - (RSCardView *)cardForIndexPath:(NSIndexPath *)indexPath { 77 | RSCardView *card = (RSCardView *)[self viewWithTag:kTagBase + kSectionSpan * indexPath.section + indexPath.row]; 78 | 79 | if (!card && __dataSource && [__dataSource respondsToSelector:@selector(cardsView:cardForRowAtIndexPath:)]) { 80 | card = [__dataSource cardsView:self cardForRowAtIndexPath:indexPath]; 81 | card.tag = kTagBase + kSectionSpan * indexPath.section + indexPath.row; 82 | } 83 | 84 | return card; 85 | } 86 | 87 | #pragma mark - Private: manipulate local data 88 | 89 | - (int)lastSection { 90 | return [[self indexPaths] count] - 1; 91 | } 92 | 93 | - (int)lastRowInSection:(int)section { 94 | return [[self indexPaths][section] count] - 1; 95 | } 96 | 97 | - (void)layout { 98 | if (!_indexPaths) { 99 | return; 100 | } 101 | 102 | CGFloat y = 0; 103 | 104 | if (__delegate && [__delegate respondsToSelector:@selector(heightForHeaderInCardsView:)]) { 105 | y += [__delegate heightForHeaderInCardsView:self]; 106 | } 107 | 108 | CGFloat sectionOffset = 0; 109 | 110 | if (__delegate && [__delegate respondsToSelector:@selector(heightForSeparatorInCardsView:)]) { 111 | sectionOffset = [__delegate heightForSeparatorInCardsView:self]; 112 | } 113 | 114 | CGFloat rowOffset = 0; 115 | 116 | if (__delegate && [__delegate respondsToSelector:@selector(heightForCoveredRowInCardsView:)]) { 117 | rowOffset = [__delegate heightForCoveredRowInCardsView:self]; 118 | } 119 | 120 | for (int section = 0; section <= [self lastSection]; section++) { 121 | for (int row = 0; row <= [self lastRowInSection:section]; row++) { 122 | RSCardView *c = [self cardForIndexPath:[NSIndexPath indexPathForRow:row inSection:section]]; 123 | CGRect frame = c.frame; 124 | frame.origin.y = y; 125 | c.frame = frame; 126 | 127 | if (row == [self lastRowInSection:section]) { 128 | y += frame.size.height; 129 | } else { 130 | y += rowOffset; 131 | } 132 | } 133 | 134 | if (section != [self lastSection]) { 135 | y += sectionOffset; 136 | } 137 | } 138 | 139 | if (__delegate && [__delegate respondsToSelector:@selector(heightForFooterInCardsView:)]) { 140 | y += [__delegate heightForFooterInCardsView:self]; 141 | } 142 | 143 | self.contentSize = CGSizeMake(self.contentSize.width, y); 144 | } 145 | 146 | #pragma mark - Private: manipulate data from data source 147 | 148 | - (int)dsLastSection { 149 | return [self dsSections] - 1; 150 | } 151 | 152 | - (int)dsLastRowInSection:(int)section { 153 | return [self dsLastRowInSection:section] - 1; 154 | } 155 | 156 | - (int)dsSections { 157 | if (__dataSource && [__dataSource respondsToSelector:@selector(numberOfSectionsInCardsView:)]) { 158 | return [__dataSource numberOfSectionsInCardsView:self]; 159 | } 160 | 161 | return 1; 162 | } 163 | 164 | - (int)dsRowsInSection:(int)section { 165 | if (__dataSource && [__dataSource respondsToSelector:@selector(cardsView:numberOfRowsInSection:)]) { 166 | return [__dataSource cardsView:self numberOfRowsInSection:section]; 167 | } 168 | 169 | return 0; 170 | } 171 | 172 | - (void)dsReload { 173 | for (UIView *view in [self subviews]) { 174 | if ([view isKindOfClass:[RSCardView class]]) { 175 | [view removeFromSuperview]; 176 | } 177 | } 178 | 179 | if (_indexPaths) { 180 | [_indexPaths removeAllObjects]; 181 | } 182 | 183 | for (int section = 0; section < [self dsSections]; section++) { 184 | int numberOfRows = [self dsRowsInSection:section]; 185 | 186 | if (numberOfRows > 0) { 187 | NSMutableArray *rows = [NSMutableArray arrayWithCapacity:numberOfRows]; 188 | 189 | for (int row = 0; row < numberOfRows; row++) { 190 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; 191 | [rows addObject:indexPath]; 192 | RSCardView *card = [self cardForIndexPath:indexPath]; 193 | 194 | if (card) { 195 | [self addSubview:card]; 196 | } 197 | } 198 | 199 | [[self indexPaths] addObject:rows]; 200 | } 201 | } 202 | } 203 | 204 | - (void)dsLayout { 205 | if (!__dataSource || !_indexPaths) { 206 | return; 207 | } 208 | 209 | CGFloat y = 0; 210 | 211 | if (__delegate && [__delegate respondsToSelector:@selector(heightForHeaderInCardsView:)]) { 212 | y += [__delegate heightForHeaderInCardsView:self]; 213 | } 214 | 215 | CGFloat sectionOffset = 0; 216 | 217 | if (__delegate && [__delegate respondsToSelector:@selector(heightForSeparatorInCardsView:)]) { 218 | sectionOffset = [__delegate heightForSeparatorInCardsView:self]; 219 | } 220 | 221 | CGFloat rowOffset = 0; 222 | 223 | if (__delegate && [__delegate respondsToSelector:@selector(heightForCoveredRowInCardsView:)]) { 224 | rowOffset = [__delegate heightForCoveredRowInCardsView:self]; 225 | } 226 | 227 | for (int section = 0; section < [self dsSections]; section++) { 228 | for (int row = 0; row < [self dsRowsInSection:section]; row++) { 229 | RSCardView *card = [self cardForIndexPath:[NSIndexPath indexPathForRow:row inSection:section]]; 230 | CGRect frame = card.frame; 231 | frame.origin.y = y; 232 | card.frame = frame; 233 | 234 | if (row == ([__dataSource cardsView:self numberOfRowsInSection:section] - 1)) { 235 | y += frame.size.height; 236 | } else { 237 | y += rowOffset; 238 | } 239 | } 240 | 241 | if (section != [self dsLastSection]) { 242 | y += sectionOffset; 243 | } 244 | } 245 | 246 | if (__delegate && [__delegate respondsToSelector:@selector(heightForFooterInCardsView:)]) { 247 | y += [__delegate heightForFooterInCardsView:self]; 248 | } 249 | 250 | self.contentSize = CGSizeMake(self.contentSize.width, y); 251 | } 252 | 253 | - (void)reloadData { 254 | [self setUserInteractionEnabled:NO]; 255 | 256 | [self dsReload]; 257 | [self dsLayout]; 258 | 259 | _isReloading = NO; 260 | _isInserting = NO; 261 | 262 | [self insertQueuedCard]; 263 | 264 | [self setUserInteractionEnabled:YES]; 265 | } 266 | 267 | #pragma mark - Private : animation related 268 | 269 | - (void)sortZPositionInSection:(int)section shouldReset:(BOOL)shouldReset { 270 | for (int row = 0; row <= [self lastRowInSection:section]; row++) { 271 | RSCardView *card = [self cardForIndexPath:[NSIndexPath indexPathForRow:row inSection:section]]; 272 | card.layer.zPosition = shouldReset ? 0 : row; 273 | } 274 | } 275 | 276 | - (void)foldInSection:(int)section { 277 | for (int row = 0; row <= [self lastRowInSection:section]; row++) { 278 | RSCardView *card = [self cardForIndexPath:[NSIndexPath indexPathForRow:row inSection:section]]; 279 | 280 | if ([card isSettingsVisible]) { 281 | [card toggleSettings]; 282 | } 283 | } 284 | } 285 | 286 | - (void)animationWillStart:(RSCardView *)card { 287 | [self setUserInteractionEnabled:NO]; 288 | 289 | int section = [self sectionForCard:card]; 290 | [self foldInSection:section]; 291 | 292 | if (_animationStyle == RSCardsViewAnimationStyleExchange) { 293 | if (self.delegate && [self.delegate respondsToSelector:@selector(cardViewWillExchangeAtIndexPath:withIndexPath:)]) { 294 | [self.delegate 295 | cardViewWillExchangeAtIndexPath:[self indexPathForCard:card] 296 | withIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] 297 | inSection:section]]; 298 | } 299 | 300 | [self exchangeAnimationScale:card]; 301 | } else if (_animationStyle == RSCardsViewAnimationStyleDrop) { 302 | if (self.delegate && [self.delegate respondsToSelector:@selector(cardViewWillDropAtIndexPath:)]) { 303 | [self.delegate cardViewWillDropAtIndexPath:[self indexPathForCard:card]]; 304 | } 305 | 306 | [self dropAnimationScale:card]; 307 | } 308 | } 309 | 310 | - (void)animationDidFinish:(RSCardView *)card { 311 | if (_animationStyle == RSCardsViewAnimationStyleExchange) { 312 | if (self.delegate && [self.delegate respondsToSelector:@selector(cardViewDidExchangeAtIndexPath:withIndexPath:)]) { 313 | int section = [self sectionForCard:card]; 314 | [self.delegate 315 | cardViewDidExchangeAtIndexPath:[self indexPathForCard:card] 316 | withIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] 317 | inSection:section]]; 318 | } 319 | } else if (_animationStyle == RSCardsViewAnimationStyleDrop) { 320 | if (self.delegate && [self.delegate respondsToSelector:@selector(cardViewDidDropAtIndexPath:)]) { 321 | [self.delegate cardViewDidDropAtIndexPath:[self indexPathForCard:card]]; 322 | } 323 | } 324 | 325 | [self setUserInteractionEnabled:YES]; 326 | } 327 | 328 | #pragma mark - Private : drop animation 329 | 330 | - (void)dropAnimationScale:(RSCardView *)card { 331 | card.layer.zPosition = 1; 332 | 333 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; 334 | animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 335 | animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(kRSDropAnimationScaleZoomOut, kRSDropAnimationScaleZoomOut, 1.0)]; 336 | animation.fillMode = kCAFillModeForwards; 337 | animation.duration = kRSAnimationDuration; 338 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 339 | card.layer.transform = CATransform3DMakeScale(kRSDropAnimationScaleZoomOut, kRSDropAnimationScaleZoomOut, 1.0); 340 | [card.layer addAnimation:animation forKey:@"scale"]; 341 | 342 | [self performSelector:@selector(dropAnimationMove:) withObject:card afterDelay:kRSAnimationDuration]; 343 | } 344 | 345 | - (void)dropAnimationMove:(RSCardView *)card { 346 | int section = [self sectionForCard:card]; 347 | RSCardView *lastCard = [self cardForIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] inSection:section]]; 348 | 349 | CGPoint lastPosition = CGPointMake(lastCard.layer.position.x, lastCard.layer.position.y - lastCard.layer.bounds.size.height / 2.f + card.layer.bounds.size.height / 2.f); 350 | 351 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; 352 | 353 | animation.fromValue = [card.layer valueForKey:@"position"]; 354 | animation.toValue = [NSValue valueWithCGPoint:lastPosition]; 355 | animation.duration = kRSAnimationDuration; 356 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 357 | card.layer.position = lastPosition; 358 | [card.layer addAnimation:animation forKey:@"position"]; 359 | 360 | CGFloat offsetRow = 0; 361 | 362 | if (self.delegate && [self.delegate respondsToSelector:@selector(heightForCoveredRowInCardsView:)]) { 363 | offsetRow = [self.delegate heightForCoveredRowInCardsView:self]; 364 | } 365 | 366 | for (int row = [self rowForCard:card] + 1; row <= [self lastRowInSection:section]; row++) { 367 | RSCardView *c = [self cardForIndexPath:[NSIndexPath indexPathForRow:row inSection:section]]; 368 | CGPoint position = c.layer.position; 369 | position.y -= offsetRow; 370 | animation = [CABasicAnimation animationWithKeyPath:@"position"]; 371 | animation.fromValue = [c.layer valueForKey:@"position"]; 372 | animation.toValue = [NSValue valueWithCGPoint:position]; 373 | animation.duration = kRSAnimationDuration; 374 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 375 | c.layer.position = position; 376 | [c.layer addAnimation:animation forKey:@"position"]; 377 | } 378 | 379 | [self performSelector:@selector(dropAnimationScaleBack:) withObject:card afterDelay:kRSAnimationDuration]; 380 | } 381 | 382 | - (void)dropAnimationScaleBack:(RSCardView *)card { 383 | [self bringSubviewToFront:card]; 384 | 385 | card.layer.zPosition = 0; 386 | 387 | int section = [self sectionForCard:card]; 388 | RSCardView *lastCard = [self cardForIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] inSection:section]]; 389 | int tag = lastCard.tag; 390 | 391 | RSCardView *firstCard = nil; 392 | 393 | for (int row = [self rowForCard:card] + 1; row <= [self lastRowInSection:section]; row++) { 394 | RSCardView *c = [self cardForIndexPath:[NSIndexPath indexPathForRow:row inSection:section]]; 395 | 396 | if (!firstCard) { 397 | firstCard = c; 398 | } 399 | 400 | c.tag -= 1; 401 | } 402 | 403 | card.tag = tag; 404 | 405 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; 406 | animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(kRSDropAnimationScaleZoomOut, kRSDropAnimationScaleZoomOut, 1.0)]; 407 | animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 408 | animation.fillMode = kCAFillModeForwards; 409 | animation.duration = kRSAnimationDuration; 410 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 411 | card.layer.transform = CATransform3DIdentity; 412 | [card.layer addAnimation:animation forKey:@"scale"]; 413 | 414 | if (card.shouldOpenSettingsLater) { 415 | [card toggleSettings]; 416 | card.shouldOpenSettingsLater = NO; 417 | } 418 | 419 | [self performSelector:@selector(animationDidFinish:) withObject:firstCard afterDelay:kRSAnimationDuration]; 420 | } 421 | 422 | #pragma mark - Private : exchange animation 423 | 424 | - (void)exchangeAnimationScale:(RSCardView *)card { 425 | int section = [self sectionForCard:card]; 426 | 427 | [self sortZPositionInSection:section shouldReset:NO]; 428 | 429 | RSCardView *lastCard = [self cardForIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] inSection:section]]; 430 | int zPosition = lastCard.layer.zPosition; 431 | lastCard.layer.zPosition = card.layer.zPosition; 432 | card.layer.zPosition = zPosition; 433 | 434 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; 435 | animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 436 | animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(kRSExchangeAnimationScaleZoomOut, kRSExchangeAnimationScaleZoomOut, 1.0)]; 437 | animation.fillMode = kCAFillModeForwards; 438 | animation.duration = kRSAnimationDuration; 439 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 440 | card.layer.transform = CATransform3DMakeScale(kRSExchangeAnimationScaleZoomOut, kRSExchangeAnimationScaleZoomOut, 1.0); 441 | [card.layer addAnimation:animation forKey:@"scale"]; 442 | 443 | animation = [CABasicAnimation animationWithKeyPath:@"transform"]; 444 | animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 445 | animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(kRSExchangeAnimationScaleZoomIn, kRSExchangeAnimationScaleZoomIn, 1.0)]; 446 | animation.fillMode = kCAFillModeForwards; 447 | animation.duration = kRSAnimationDuration; 448 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 449 | lastCard.layer.transform = CATransform3DMakeScale(kRSExchangeAnimationScaleZoomIn, kRSExchangeAnimationScaleZoomIn, 1.0); 450 | [lastCard.layer addAnimation:animation forKey:@"scale"]; 451 | 452 | [self performSelector:@selector(exchangeAnimationMove:) withObject:card afterDelay:kRSAnimationDuration]; 453 | } 454 | 455 | - (void)exchangeAnimationMove:(RSCardView *)card { 456 | int section = [self sectionForCard:card]; 457 | RSCardView *lastCard = [self cardForIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] inSection:section]]; 458 | 459 | CGPoint position = CGPointMake(card.layer.position.x, card.layer.position.y - card.layer.bounds.size.height / 2.f + lastCard.layer.bounds.size.height / 2.f); 460 | CGPoint lastPosition = CGPointMake(lastCard.layer.position.x, lastCard.layer.position.y - lastCard.layer.bounds.size.height / 2.f + card.layer.bounds.size.height / 2.f); 461 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; 462 | 463 | animation.fromValue = [card.layer valueForKey:@"position"]; 464 | animation.toValue = [NSValue valueWithCGPoint:lastPosition]; 465 | animation.duration = kRSAnimationDuration; 466 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 467 | card.layer.position = lastPosition; 468 | [card.layer addAnimation:animation forKey:@"position"]; 469 | 470 | animation = [CABasicAnimation animationWithKeyPath:@"position"]; 471 | animation.fromValue = [lastCard.layer valueForKey:@"position"]; 472 | animation.toValue = [NSValue valueWithCGPoint:position]; 473 | animation.duration = kRSAnimationDuration; 474 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 475 | lastCard.layer.position = position; 476 | [lastCard.layer addAnimation:animation forKey:@"position"]; 477 | 478 | [self performSelector:@selector(exchangeAnimationScaleBack:) withObject:card afterDelay:kRSAnimationDuration]; 479 | } 480 | 481 | - (void)exchangeAnimationScaleBack:(RSCardView *)card { 482 | int section = [self sectionForCard:card]; 483 | 484 | [self sortZPositionInSection:section shouldReset:YES]; 485 | 486 | RSCardView *lastCard = [self cardForIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] inSection:section]]; 487 | 488 | [self exchangeSubviewAtIndex:[self.subviews indexOfObject:card] withSubviewAtIndex:[self.subviews indexOfObject:lastCard]]; 489 | 490 | NSInteger tag = card.tag; 491 | card.tag = lastCard.tag; 492 | lastCard.tag = tag; 493 | 494 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; 495 | animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(kRSExchangeAnimationScaleZoomOut, kRSExchangeAnimationScaleZoomOut, 1.0)]; 496 | animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 497 | animation.fillMode = kCAFillModeForwards; 498 | animation.duration = kRSAnimationDuration; 499 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 500 | card.layer.transform = CATransform3DIdentity; 501 | [card.layer addAnimation:animation forKey:@"scale"]; 502 | 503 | animation = [CABasicAnimation animationWithKeyPath:@"transform"]; 504 | animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(kRSExchangeAnimationScaleZoomIn, kRSExchangeAnimationScaleZoomIn, 1.0)]; 505 | animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 506 | animation.fillMode = kCAFillModeForwards; 507 | animation.duration = kRSAnimationDuration; 508 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 509 | lastCard.layer.transform = CATransform3DIdentity; 510 | [lastCard.layer addAnimation:animation forKey:@"scale"]; 511 | 512 | if (card.shouldOpenSettingsLater) { 513 | [card toggleSettings]; 514 | card.shouldOpenSettingsLater = NO; 515 | } 516 | 517 | [self performSelector:@selector(animationDidFinish:) withObject:lastCard afterDelay:kRSAnimationDuration]; 518 | } 519 | 520 | #pragma mark - UIScrollView 521 | 522 | - (id)initWithFrame:(CGRect)frame { 523 | self = [super initWithFrame:frame]; 524 | 525 | if (self) { 526 | // Initialization code 527 | self.backgroundColor = [UIColor whiteColor]; 528 | 529 | _indexPaths = [[NSMutableArray alloc] init]; 530 | 531 | _animationStyle = RSCardsViewAnimationStyleExchange; 532 | } 533 | 534 | return self; 535 | } 536 | 537 | - (void)dealloc { 538 | [_indexPaths removeAllObjects]; 539 | [_indexPaths release]; 540 | [_insertQueue removeAllObjects]; 541 | [_insertQueue release]; 542 | [super dealloc]; 543 | } 544 | 545 | #pragma mark - Public 546 | 547 | - (void)setNeedsReload { 548 | if (!_isReloading) { 549 | _isReloading = YES; 550 | _isInserting = YES; 551 | __block typeof(self) this = self; 552 | dispatch_async(dispatch_get_main_queue(), ^{ 553 | [this reloadData]; 554 | }); 555 | } 556 | } 557 | 558 | - (void)insertCard:(RSCardView *)card { 559 | if (_isInserting) { 560 | [[self insertQueue] addObject:card]; 561 | return; 562 | } 563 | 564 | [self setUserInteractionEnabled:NO]; 565 | 566 | _isInserting = YES; 567 | 568 | NSString *cardViewClassName = NSStringFromClass([card class]); 569 | Class cardClass = NSClassFromString([cardViewClassName substringToIndex:[cardViewClassName rangeOfString:@"View"].location]); 570 | 571 | #pragma clang diagnostic push 572 | #pragma clang diagnostic ignored "-Wobjc-method-access" 573 | 574 | if ([cardClass shouldInsertInSection]) { 575 | #pragma clang diagnostic pop 576 | 577 | for (int section = 0; section <= [self lastSection]; section++) { 578 | for (int row = 0; row <= [self lastRowInSection:section]; row++) { 579 | RSCardView *c = [self cardForIndexPath:[NSIndexPath indexPathForRow:row inSection:section]]; 580 | 581 | if ([card isKindOfClass:[c class]]) { 582 | int r = [self lastRowInSection:section] + 1; 583 | card.tag = kTagBase + kSectionSpan * section + r; 584 | NSIndexPath *ip = [NSIndexPath indexPathForRow:r inSection:section]; 585 | [[self indexPaths][section] addObject:ip]; 586 | 587 | goto Animation; 588 | } 589 | } 590 | } 591 | } 592 | 593 | for (int section = 0; section <= [self lastSection]; section++) { 594 | for (int row = 0; row <= [self lastRowInSection:section]; row++) { 595 | [self cardForIndexPath:[NSIndexPath indexPathForRow:row inSection:section]].tag = kTagBase + kSectionSpan * (section + 1) + row; 596 | [self indexPaths][section][row] = [NSIndexPath indexPathForRow:row inSection:section + 1]; 597 | } 598 | } 599 | 600 | NSMutableArray *section = [NSMutableArray arrayWithCapacity:1]; 601 | [section addObject:[NSIndexPath indexPathForRow:0 inSection:0]]; 602 | [[self indexPaths] insertObject:section atIndex:0]; 603 | card.tag = kTagBase; 604 | 605 | Animation: 606 | 607 | card.hidden = YES; 608 | [self addSubview:card]; 609 | 610 | [UIView animateWithDuration:kRSAnimationDuration 611 | animations: ^{ 612 | [self layout]; 613 | } 614 | 615 | completion: ^(BOOL finished) { 616 | [self setUserInteractionEnabled:YES]; 617 | card.hidden = NO; 618 | [card insertAnimation]; 619 | }]; 620 | } 621 | 622 | #pragma mark - RSCardViewDelegate 623 | 624 | - (UIColor *)superviewBackgroundColor { 625 | return self.backgroundColor; 626 | } 627 | 628 | - (BOOL)canToggleSettings:(RSCardView *)card { 629 | int section = [self sectionForCard:card]; 630 | RSCardView *lastCard = [self cardForIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] inSection:section]]; 631 | 632 | return lastCard == card; 633 | } 634 | 635 | - (void)didTapOnCard:(RSCardView *)card { 636 | int section = [self sectionForCard:card]; 637 | RSCardView *lastCard = [self cardForIndexPath:[NSIndexPath indexPathForRow:[self lastRowInSection:section] inSection:section]]; 638 | 639 | if (card != lastCard) { 640 | [self animationWillStart:card]; 641 | } 642 | } 643 | 644 | - (void)didRemoveFromSuperview:(RSCardView *)card { 645 | [self setUserInteractionEnabled:NO]; 646 | 647 | NSIndexPath *indexPath = [self indexPathForCard:card]; 648 | 649 | if (__delegate && [__delegate respondsToSelector:@selector(cardViewDidRemoveAtIndexPath:)]) { 650 | [__delegate cardViewDidRemoveAtIndexPath:indexPath]; 651 | } 652 | 653 | [[self indexPaths][indexPath.section] removeObjectAtIndex:indexPath.row]; 654 | 655 | int section = [self sectionForCard:card]; 656 | 657 | if ([self lastRowInSection:section] >= 0) { 658 | for (int i = [self rowForCard:card]; i <= [self lastRowInSection:section]; i++) { 659 | NSIndexPath *indexPath = [self indexPaths][section][i]; 660 | [self cardForIndexPath:indexPath].tag -= 1; 661 | [self indexPaths][section][i] = [NSIndexPath indexPathForRow:[indexPath row] - 1 inSection:[indexPath section]]; 662 | } 663 | } else { 664 | [[self indexPaths] removeObjectAtIndex:section]; 665 | 666 | for (int i = section; i <= [self lastSection]; i++) { 667 | for (int j = 0; j <= [self lastRowInSection:section]; j++) { 668 | NSIndexPath *indexPath = [self indexPaths][i][j]; 669 | [self cardForIndexPath:indexPath].tag -= kSectionSpan; 670 | [self indexPaths][i][j] = [NSIndexPath indexPathForRow:[indexPath row] inSection:[indexPath section] - 1]; 671 | } 672 | } 673 | } 674 | 675 | [UIView animateWithDuration:kRSAnimationDuration 676 | animations: ^{ 677 | [self layout]; 678 | } 679 | 680 | completion: ^(BOOL finished) { 681 | [self setUserInteractionEnabled:YES]; 682 | }]; 683 | } 684 | 685 | - (void)didChangeFrame:(RSCardView *)cardView { 686 | [self setUserInteractionEnabled:NO]; 687 | 688 | [UIView animateWithDuration:kRSAnimationDuration 689 | animations: ^{ 690 | [self layout]; 691 | } 692 | 693 | completion: ^(BOOL finished) { 694 | [self setUserInteractionEnabled:YES]; 695 | }]; 696 | } 697 | 698 | - (void)insertAnimationDidStart { 699 | [self setUserInteractionEnabled:NO]; 700 | } 701 | 702 | - (void)insertAnimationDidStop { 703 | [self setUserInteractionEnabled:YES]; 704 | 705 | _isInserting = NO; 706 | 707 | [self insertQueuedCard]; 708 | } 709 | 710 | @end 711 | --------------------------------------------------------------------------------