├── 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 | 
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 | [](http://v.youku.com/v_show/id_XNTc4MDUyODY0.html)
21 |
22 |
23 |
24 |
25 | [](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 |
--------------------------------------------------------------------------------