├── .gitignore ├── images ├── demo.gif └── MTCardLayout.png ├── Samples └── Passbooks │ ├── Passbooks │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── delete.imageset │ │ │ ├── delete@2x.png │ │ │ └── Contents.json │ │ ├── delete_small.imageset │ │ │ ├── delete_small@2x.png │ │ │ └── Contents.json │ │ ├── LaunchImage.launchimage │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.h │ ├── ViewController.h │ ├── main.m │ ├── AppDelegate.m │ ├── Passbooks-Prefix.pch │ ├── PassCell.h │ ├── SearchViewController.h │ ├── Passbooks-Info.plist │ ├── SearchViewController.m │ ├── PassCell.m │ ├── Launch.storyboard │ ├── ViewController.m │ └── Base.lproj │ │ └── Main.storyboard │ ├── PassbooksTests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── PassbooksTests.m │ └── PassbooksTests-Info.plist │ └── Passbooks.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── Passbooks.xcscmblueprint │ │ └── Passbooks.xccheckout │ └── project.pbxproj ├── MTCardLayout ├── MTDraggableCardLayout.h ├── UICollectionView+DraggableCardLayout.h ├── CardLayout.h ├── MTCardLayout.h ├── MTCardLayoutHelper.h ├── UICollectionView+CardLayout.h ├── MTDraggableCardLayoutHelper.h ├── UICollectionView+DraggableCardLayout.m ├── MTDraggableCardLayout.m ├── UICollectionView+CardLayout.m ├── MTCommonTypes.h ├── MTCardLayoutHelper.m ├── MTCardLayout.m └── MTDraggableCardLayoutHelper.m ├── MTCardLayout.podspec ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/xcuserdata/** 2 | **/Pods/** 3 | **/Podfile.lock 4 | -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minhntran/MTCardLayout/HEAD/images/demo.gif -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /images/MTCardLayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minhntran/MTCardLayout/HEAD/images/MTCardLayout.png -------------------------------------------------------------------------------- /Samples/Passbooks/PassbooksTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /MTCardLayout/MTDraggableCardLayout.h: -------------------------------------------------------------------------------- 1 | #import "MTCardLayout.h" 2 | 3 | @interface MTDraggableCardLayout : MTCardLayout 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MTCardLayout/UICollectionView+DraggableCardLayout.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface UICollectionView (DraggableCardLayout) 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /MTCardLayout/CardLayout.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "UICollectionView+CardLayout.h" 3 | #import "UICollectionView+DraggableCardLayout.h" 4 | #import "MTDraggableCardLayout.h" 5 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Images.xcassets/delete.imageset/delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minhntran/MTCardLayout/HEAD/Samples/Passbooks/Passbooks/Images.xcassets/delete.imageset/delete@2x.png -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AppDelegate : UIResponder 4 | 5 | @property (strong, nonatomic) UIWindow *window; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Images.xcassets/delete_small.imageset/delete_small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minhntran/MTCardLayout/HEAD/Samples/Passbooks/Passbooks/Images.xcassets/delete_small.imageset/delete_small@2x.png -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/ViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ViewController : UIViewController 4 | 5 | @property (nonatomic, strong) IBOutlet UICollectionView *collectionView; 6 | 7 | - (IBAction)flip:(id)sender; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MTCardLayout/MTCardLayout.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "MTCommonTypes.h" 3 | 4 | @interface MTCardLayout : UICollectionViewLayout 5 | 6 | @property (nonatomic, assign) MTCardLayoutMetrics metrics; 7 | @property (nonatomic, assign) MTCardLayoutEffects effects; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | @implementation AppDelegate 4 | 5 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 6 | { 7 | // Override point for customization after application launch. 8 | return YES; 9 | } 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /MTCardLayout/MTCardLayoutHelper.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "MTCommonTypes.h" 3 | 4 | @interface MTCardLayoutHelper : NSObject 5 | 6 | @property (nonatomic) MTCardLayoutViewMode viewMode; 7 | 8 | - (id)initWithCollectionView:(UICollectionView *)collectionView; 9 | - (void)unbindFromCollectionView:(UICollectionView *)collectionView; 10 | 11 | @end -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Passbooks-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Images.xcassets/delete.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "delete@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/PassCell.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface PassCell : UICollectionViewCell 4 | 5 | @property (nonatomic) IBOutlet UILabel *titleLabel; 6 | @property (nonatomic) IBOutlet UIButton *infoButton; 7 | @property (nonatomic) IBOutlet UIButton *doneButton; 8 | 9 | - (void)flipTransitionWithOptions:(UIViewAnimationOptions)options halfway:(void (^)(BOOL finished))halfway completion:(void (^)(BOOL finished))completion; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Images.xcassets/delete_small.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "delete_small@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MTCardLayout/UICollectionView+CardLayout.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "MTCommonTypes.h" 3 | 4 | @interface UICollectionView (CardLayout) 5 | 6 | @property (nonatomic) MTCardLayoutViewMode viewMode; 7 | 8 | - (void)setViewMode:(MTCardLayoutViewMode)viewMode animated:(BOOL)animated completion:(void (^)(BOOL))completion; 9 | 10 | - (void)selectAndNotifyDelegate:(NSIndexPath *)indexPath; 11 | - (void)deselectAndNotifyDelegate:(NSIndexPath *)indexPath; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /MTCardLayout/MTDraggableCardLayoutHelper.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface MTDraggableCardLayoutHelper : NSObject 4 | 5 | @property (nonatomic, readonly) UICollectionViewLayoutAttributes *movingItemAttributes; 6 | @property (nonatomic, readonly) NSIndexPath *toIndexPath; 7 | @property (nonatomic, readonly) CGRect movingItemFrame; 8 | @property (nonatomic, readonly) CGFloat movingItemAlpha; 9 | 10 | - (id)initWithCollectionView:(UICollectionView *)collectionView; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/SearchViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class SearchViewController; 4 | 5 | @protocol SearchViewControllerDelegate 6 | - (void)searchControllerWillBeginSearch:(SearchViewController *)controller; 7 | - (void)searchControllerWillEndSearch:(SearchViewController *)controller; 8 | @end 9 | 10 | @interface SearchViewController : UIViewController 11 | @property (nonatomic, weak) id delegate; 12 | @property (nonatomic, strong) IBOutlet UISearchBar *searchBar; 13 | @property (nonatomic, strong) IBOutlet UITableView *tableView; 14 | @end 15 | -------------------------------------------------------------------------------- /Samples/Passbooks/PassbooksTests/PassbooksTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface PassbooksTests : XCTestCase 4 | 5 | @end 6 | 7 | @implementation PassbooksTests 8 | 9 | - (void)setUp 10 | { 11 | [super setUp]; 12 | // Put setup code here. This method is called before the invocation of each test method in the class. 13 | } 14 | 15 | - (void)tearDown 16 | { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | [super tearDown]; 19 | } 20 | 21 | - (void)testExample 22 | { 23 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /MTCardLayout/UICollectionView+DraggableCardLayout.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "MTDraggableCardLayoutHelper.h" 3 | 4 | static const char MTDraggableCardLayoutHelperKey; 5 | 6 | @implementation UICollectionView (DraggableCardLayout) 7 | 8 | - (MTDraggableCardLayoutHelper *)draggableCardLayoutHelper 9 | { 10 | MTDraggableCardLayoutHelper *helper = objc_getAssociatedObject(self, &MTDraggableCardLayoutHelperKey); 11 | if(helper == nil) { 12 | helper = [[MTDraggableCardLayoutHelper alloc] initWithCollectionView:self]; 13 | objc_setAssociatedObject(self, &MTDraggableCardLayoutHelperKey, helper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 14 | } 15 | return helper; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Samples/Passbooks/PassbooksTests/PassbooksTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /MTCardLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "MTCardLayout" 4 | s.version = "2.0.1" 5 | s.summary = "UICollectionView layout to mimick Passbook app." 6 | 7 | s.description = <<-DESC 8 | Apple has released awesome card layout for apps like Passbook or Reminder, 9 | how ever they do not release the layout with the SDK. 10 | 11 | This is an attempt to recreate the layout/animation as much as possible. 12 | DESC 13 | 14 | s.homepage = "https://github.com/minhntran/MTCardLayout" 15 | s.screenshots = "https://raw.githubusercontent.com/minhntran/MTCardLayout/master/images/demo.gif" 16 | s.license = { :type => "MIT", :file => "LICENSE.md" } 17 | s.author = "Minh Tran" 18 | s.social_media_url = "http://twitter.com/zealix" 19 | s.platform = :ios, "7.0" 20 | s.source = { :git => "https://github.com/minhntran/MTCardLayout.git", :tag => "v2.0.1" } 21 | s.source_files = "MTCardLayout", "MTCardLayout/**/*.{h,m}" 22 | s.public_header_files = "MTCardLayout/**/*.h" 23 | s.requires_arc = true 24 | 25 | end 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Minh N Tran 4 | 5 | This library was inspired by PassbookLayout, Copyright (c) 2013 CanTheAlmighty 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MTCardLayout (iOS 7+) 2 | =============== 3 | 4 | Mimicking the behaviour of the Passbooks apps in iOS using a custom `UICollectionViewLayout`. 5 | 6 | ![Crappy gif ahoy!](images/demo.gif) 7 | 8 | ##Installation 9 | Copy the `MTCardLayout` folder that has the `.h` and `.m` into you project. Specify MTCardLayout as layout for your UICollectionView. 10 | 11 | If you use `CocoaPods`, add pod 'MTCardLayout' to your Podfile. 12 | 13 | See the attached sample project for usage. 14 | 15 | ##Intended use 16 | This collection view is suitable for applications that want to mimick the behaviour of the included Apple Passbook and Reminder on iOS devices (iOS 6 onwards). 17 | 18 | This collection view layout is rather inneficient compared to other layouts, it invalidates for each change of bounds to support it's fancy animations. On the other hand, it only recalculates the currently visible cells, so it can support big numbers of cells, just don't make each cell expensive to redraw/rescale. 19 | 20 | And it does not use `UIDynamics`, just math. 21 | 22 | ##Credits 23 | This is a rewrite of the PassbookLayout by CanTheAlmighty (https://github.com/CanTheAlmighty/PassbookLayout). This is a clean rewrite of that class and most of the functionality. 24 | 25 | `MTCardLayout` includes a modified version of `DraggableCollectionView` by Luke Scott (https://github.com/lukescott/DraggableCollectionView). 26 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Passbooks-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 | UILaunchStoryboardName 28 | Launch 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/SearchViewController.m: -------------------------------------------------------------------------------- 1 | #import "SearchViewController.h" 2 | 3 | @interface SearchViewController () 4 | 5 | @end 6 | 7 | @implementation SearchViewController 8 | 9 | #pragma mark - Table view data source 10 | 11 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 12 | { 13 | return 1; 14 | } 15 | 16 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 17 | { 18 | return 0; 19 | } 20 | 21 | /* 22 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 23 | { 24 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<#@"reuseIdentifier"#> forIndexPath:indexPath]; 25 | 26 | // Configure the cell... 27 | 28 | return cell; 29 | } 30 | */ 31 | 32 | - (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar 33 | { 34 | [self.searchBar setShowsCancelButton:YES animated:YES]; 35 | id delegate = self.delegate; 36 | if (delegate) 37 | { 38 | [delegate searchControllerWillBeginSearch:self]; 39 | } 40 | } 41 | 42 | - (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar 43 | { 44 | [self.searchBar setShowsCancelButton:NO animated:YES]; 45 | } 46 | 47 | - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar 48 | { 49 | [searchBar resignFirstResponder]; 50 | id delegate = self.delegate; 51 | if (delegate) 52 | { 53 | [delegate searchControllerWillEndSearch:self]; 54 | } 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks.xcodeproj/project.xcworkspace/xcshareddata/Passbooks.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "3BD6FDF64705E442A9D5C7DFFCB39234E6D1307D", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "AB26675F-B85E-4326-922B-D017EDBB978F" : 0, 8 | "3BD6FDF64705E442A9D5C7DFFCB39234E6D1307D" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "A0852743-FE7C-4676-AA8E-8708FF6035FC", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "AB26675F-B85E-4326-922B-D017EDBB978F" : "PassbookLayout", 13 | "3BD6FDF64705E442A9D5C7DFFCB39234E6D1307D" : "MTCardLayout" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Passbooks", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Samples\/Passbooks\/Passbooks.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/minhntran\/MTCardLayout.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3BD6FDF64705E442A9D5C7DFFCB39234E6D1307D" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/CanTheAlmighty\/PassbookLayout.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "AB26675F-B85E-4326-922B-D017EDBB978F" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks.xcodeproj/project.xcworkspace/xcshareddata/Passbooks.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | A0852743-FE7C-4676-AA8E-8708FF6035FC 9 | IDESourceControlProjectName 10 | Passbooks 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 3BD6FDF64705E442A9D5C7DFFCB39234E6D1307D 14 | https://github.com/minhntran/MTCardLayout.git 15 | AB26675F-B85E-4326-922B-D017EDBB978F 16 | https://github.com/CanTheAlmighty/PassbookLayout.git 17 | 18 | IDESourceControlProjectPath 19 | Samples/Passbooks/Passbooks.xcodeproj 20 | IDESourceControlProjectRelativeInstallPathDictionary 21 | 22 | 3BD6FDF64705E442A9D5C7DFFCB39234E6D1307D 23 | ../../../.. 24 | AB26675F-B85E-4326-922B-D017EDBB978F 25 | ../../../../../PassbookLayout 26 | 27 | IDESourceControlProjectURL 28 | https://github.com/minhntran/MTCardLayout.git 29 | IDESourceControlProjectVersion 30 | 111 31 | IDESourceControlProjectWCCIdentifier 32 | 3BD6FDF64705E442A9D5C7DFFCB39234E6D1307D 33 | IDESourceControlProjectWCConfigurations 34 | 35 | 36 | IDESourceControlRepositoryExtensionIdentifierKey 37 | public.vcs.git 38 | IDESourceControlWCCIdentifierKey 39 | 3BD6FDF64705E442A9D5C7DFFCB39234E6D1307D 40 | IDESourceControlWCCName 41 | MTCardLayout 42 | 43 | 44 | IDESourceControlRepositoryExtensionIdentifierKey 45 | public.vcs.git 46 | IDESourceControlWCCIdentifierKey 47 | AB26675F-B85E-4326-922B-D017EDBB978F 48 | IDESourceControlWCCName 49 | PassbookLayout 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/PassCell.m: -------------------------------------------------------------------------------- 1 | #import "PassCell.h" 2 | 3 | @interface PassCell() 4 | { 5 | CGFloat _shadowWidth; 6 | } 7 | @end 8 | 9 | @implementation PassCell 10 | 11 | - (void)layoutSubviews 12 | { 13 | [super layoutSubviews]; 14 | 15 | CGRect bounds = self.bounds; 16 | if (_shadowWidth != bounds.size.width) 17 | { 18 | if (_shadowWidth == 0) 19 | { 20 | [self.layer setMasksToBounds:NO ]; 21 | [self.layer setShadowColor:[[UIColor blackColor ] CGColor ] ]; 22 | [self.layer setShadowOpacity:0.5 ]; 23 | [self.layer setShadowRadius:5.0 ]; 24 | [self.layer setShadowOffset:CGSizeMake( 0 , 0 ) ]; 25 | self.layer.cornerRadius = 5.0; 26 | } 27 | [self.layer setShadowPath:[[UIBezierPath bezierPathWithRect:bounds ] CGPath ] ]; 28 | _shadowWidth = bounds.size.width; 29 | } 30 | } 31 | 32 | - (void)flipTransitionWithOptions:(UIViewAnimationOptions)options halfway:(void (^)(BOOL finished))halfway completion:(void (^)(BOOL finished))completion 33 | { 34 | CGFloat degree = (options & UIViewAnimationOptionTransitionFlipFromRight) ? -M_PI_2 : M_PI_2; 35 | 36 | CGFloat duration = 0.4; 37 | CGFloat distanceZ = 2000; 38 | CGFloat translationZ = self.frame.size.width / 2; 39 | CGFloat scaleXY = (distanceZ - translationZ) / distanceZ; 40 | 41 | CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity; 42 | rotationAndPerspectiveTransform.m34 = 1.0 / -distanceZ; // perspective 43 | rotationAndPerspectiveTransform = CATransform3DTranslate(rotationAndPerspectiveTransform, 0, 0, translationZ); 44 | 45 | rotationAndPerspectiveTransform = CATransform3DScale(rotationAndPerspectiveTransform, scaleXY, scaleXY, 1.0); 46 | self.layer.transform = rotationAndPerspectiveTransform; 47 | 48 | [UIView animateWithDuration:duration / 2 animations:^{ 49 | self.layer.transform = CATransform3DRotate(rotationAndPerspectiveTransform, degree, 0.0f, 1.0f, 0.0f); 50 | } completion:^(BOOL finished){ 51 | if (halfway) halfway(finished); 52 | self.layer.transform = CATransform3DRotate(rotationAndPerspectiveTransform, -degree, 0.0f, 1.0f, 0.0f); 53 | [UIView animateWithDuration:duration / 2 animations:^{ 54 | self.layer.transform = rotationAndPerspectiveTransform; 55 | } completion:^(BOOL finished){ 56 | self.layer.transform = CATransform3DIdentity; 57 | if (completion) completion(finished); 58 | }]; 59 | }]; 60 | } 61 | @end 62 | -------------------------------------------------------------------------------- /MTCardLayout/MTDraggableCardLayout.m: -------------------------------------------------------------------------------- 1 | #import "MTDraggableCardLayout.h" 2 | #import "UICollectionView+CardLayout.h" 3 | #import "UICollectionView+DraggableCardLayout.h" 4 | #import "MTDraggableCardLayoutHelper.h" 5 | 6 | @interface UICollectionView (DraggableCardLayoutPrivate) 7 | 8 | @property (nonatomic, readonly) MTDraggableCardLayoutHelper *draggableCardLayoutHelper; 9 | 10 | @end 11 | 12 | @interface MTDraggableCardLayout () 13 | 14 | @end 15 | 16 | @implementation MTDraggableCardLayout 17 | 18 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 19 | { 20 | NSArray *elements = [super layoutAttributesForElementsInRect:rect]; 21 | 22 | UICollectionView *collectionView = self.collectionView; 23 | NSIndexPath *fromIndexPath = collectionView.draggableCardLayoutHelper.movingItemAttributes.indexPath; 24 | NSIndexPath *toIndexPath = collectionView.draggableCardLayoutHelper.toIndexPath; 25 | 26 | if (fromIndexPath == nil || toIndexPath == nil) { 27 | return elements; 28 | } 29 | 30 | for (UICollectionViewLayoutAttributes *layoutAttributes in elements) { 31 | if(layoutAttributes.representedElementCategory != UICollectionElementCategoryCell) { 32 | continue; 33 | } 34 | NSIndexPath *indexPath = layoutAttributes.indexPath; 35 | if ([indexPath isEqual:toIndexPath]) { 36 | // Item's new location 37 | layoutAttributes.indexPath = fromIndexPath; 38 | layoutAttributes.frame = CGRectOffset(self.collectionView.draggableCardLayoutHelper.movingItemFrame, 0, -8); 39 | } 40 | else if (toIndexPath) { 41 | if(indexPath.item <= fromIndexPath.item && indexPath.item > toIndexPath.item) { 42 | // Item moved back 43 | layoutAttributes.indexPath = [NSIndexPath indexPathForItem:indexPath.item - 1 inSection:indexPath.section]; 44 | } 45 | else if(indexPath.item >= fromIndexPath.item && indexPath.item < toIndexPath.item) { 46 | // Item moved forward 47 | layoutAttributes.indexPath = [NSIndexPath indexPathForItem:indexPath.item + 1 inSection:indexPath.section]; 48 | } 49 | } 50 | } 51 | 52 | return elements; 53 | } 54 | 55 | - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath 56 | { 57 | UICollectionViewLayoutAttributes *finalAttributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath]; 58 | UICollectionViewLayoutAttributes *movingItemAttributes = self.collectionView.draggableCardLayoutHelper.movingItemAttributes; 59 | 60 | if ([movingItemAttributes.indexPath isEqual:indexPath] && 61 | self.collectionView.draggableCardLayoutHelper.toIndexPath == nil) { 62 | finalAttributes.frame = self.collectionView.draggableCardLayoutHelper.movingItemFrame; 63 | } 64 | 65 | return finalAttributes; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /MTCardLayout/UICollectionView+CardLayout.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "UICollectionView+CardLayout.h" 3 | #import "MTCardLayoutHelper.h" 4 | 5 | static const char MTCardLayoutHelperKey; 6 | 7 | @implementation UICollectionView(CardLayout) 8 | 9 | - (MTCardLayoutHelper *)cardLayoutHelper 10 | { 11 | MTCardLayoutHelper *helper = objc_getAssociatedObject(self, &MTCardLayoutHelperKey); 12 | if(helper == nil) { 13 | helper = [[MTCardLayoutHelper alloc] initWithCollectionView:self]; 14 | objc_setAssociatedObject(self, &MTCardLayoutHelperKey, helper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 15 | } 16 | return helper; 17 | } 18 | 19 | - (void)cardLayoutCleanup 20 | { 21 | MTCardLayoutHelper *helper = objc_getAssociatedObject(self, &MTCardLayoutHelperKey); 22 | if (helper) 23 | { 24 | [helper unbindFromCollectionView:self]; 25 | objc_setAssociatedObject(self, &MTCardLayoutHelperKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 26 | } 27 | } 28 | 29 | - (MTCardLayoutViewMode)viewMode 30 | { 31 | return self.cardLayoutHelper.viewMode; 32 | } 33 | 34 | - (void)setViewMode:(MTCardLayoutViewMode)viewMode 35 | { 36 | [self setViewMode:viewMode animated:NO completion:nil]; 37 | } 38 | 39 | - (void)setViewMode:(MTCardLayoutViewMode)viewMode animated:(BOOL)animated completion:(void (^)(BOOL))completion 40 | { 41 | void (^setPresenting)() = ^{ 42 | self.cardLayoutHelper.viewMode = viewMode; 43 | self.scrollEnabled = viewMode == MTCardLayoutViewModeDefault; 44 | self.scrollsToTop = viewMode == MTCardLayoutViewModeDefault; 45 | 46 | id delegate = (id)self.delegate; 47 | 48 | if ([delegate respondsToSelector:@selector(collectionViewDidChangeViewMode:)]) { 49 | [delegate collectionViewDidChangeViewMode:self]; 50 | } 51 | }; 52 | 53 | if (animated) 54 | { 55 | [self performBatchUpdates:^{ 56 | setPresenting(); 57 | } completion:^(BOOL finished) { 58 | if (completion) completion(finished); 59 | }]; 60 | } 61 | else 62 | { 63 | setPresenting(); 64 | [self.collectionViewLayout invalidateLayout]; 65 | if (completion) completion(TRUE); 66 | } 67 | } 68 | 69 | - (void)deselectAndNotifyDelegate:(NSIndexPath *)indexPath 70 | { 71 | [self deselectItemAtIndexPath:indexPath animated:NO]; 72 | if ([self.delegate respondsToSelector:@selector(collectionView:didDeselectItemAtIndexPath:)]) { 73 | [self.delegate collectionView:self didDeselectItemAtIndexPath:indexPath]; 74 | } 75 | } 76 | 77 | - (void)selectAndNotifyDelegate:(NSIndexPath *)indexPath 78 | { 79 | [self selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; 80 | if ([self.delegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]) { 81 | [self.delegate collectionView:self didSelectItemAtIndexPath:indexPath]; 82 | } 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /MTCardLayout/MTCommonTypes.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef NS_ENUM(NSInteger, MTCardLayoutViewMode) { 4 | MTCardLayoutViewModeDefault, 5 | MTCardLayoutViewModePresenting 6 | }; 7 | 8 | typedef struct 9 | { 10 | // Insets of the fullscreen card 11 | UIEdgeInsets presentingInsets; 12 | 13 | // Insets of the list 14 | UIEdgeInsets listingInsets; 15 | 16 | // Top flexible inset 17 | CGFloat flexibleTop; 18 | // The visible size of each card in the normal stack 19 | CGFloat minimumVisibleHeight; 20 | // The visible size of each card in the bottom stack 21 | CGFloat stackedVisibleHeight; 22 | // Max number of card to show at the bottom stack 23 | NSUInteger maxStackedCards; 24 | 25 | // This value is calculated internally 26 | CGFloat visibleHeight; 27 | } MTCardLayoutMetrics; 28 | 29 | typedef struct 30 | { 31 | /// How much of the pulling is translated into movement on the top. An inheritance of 0 disables this feature (same as bouncesTop) 32 | CGFloat inheritance; 33 | 34 | /// Allows for bouncing when reaching the top 35 | BOOL bouncesTop; 36 | 37 | /// Allows the cards get "stuck" on the top, instead of just scrolling outside 38 | BOOL sticksTop; 39 | 40 | /// Allows the cards to spread out when there is less number of cards 41 | BOOL spreading; 42 | 43 | /// Allows all cards to collapse to the bottom 44 | BOOL collapsesAll; 45 | 46 | } MTCardLayoutEffects; 47 | 48 | @protocol UICollectionViewDataSource_Draggable 49 | 50 | @optional 51 | 52 | - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath; 53 | - (BOOL)collectionView:(UICollectionView *)collectionView canDeleteItemAtIndexPath:(NSIndexPath *)indexPath; 54 | - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)toIndexPath; 55 | - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; 56 | - (void)collectionView:(UICollectionView *)collectionView deleteItemAtIndexPath:(NSIndexPath *)indexPath; 57 | 58 | @end 59 | 60 | @protocol UICollectionViewDelegate_Draggable 61 | 62 | @optional 63 | 64 | - (void)collectionViewDidChangeViewMode:(UICollectionView *)collectionView; 65 | - (void)collectionView:(UICollectionView *)collectionView didMoveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)toIndexPath; 66 | - (UIView *)collectionView:(UICollectionView *)collectionView deletionIndicatorViewForItemAtIndexPath:(NSIndexPath *)indexPath; 67 | - (void)collectionView:(UICollectionView *)collectionView willDeleteItemAtIndexPath:(NSIndexPath *)indexPath completion:(void(^)(BOOL cancel))completion; 68 | - (void)collectionView:(UICollectionView *)collectionView didDeleteItemAtIndexPath:(NSIndexPath *)indexPath; 69 | - (BOOL)collectionView:(UICollectionView *)collectionView shouldRecognizeTapGestureAtPoint:(CGPoint)point; 70 | - (BOOL)collectionView:(UICollectionView *)collectionView shouldRecognizePanGestureAtPoint:(CGPoint)point; 71 | 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /MTCardLayout/MTCardLayoutHelper.m: -------------------------------------------------------------------------------- 1 | #import "MTCardLayoutHelper.h" 2 | #import "UICollectionView+CardLayout.h" 3 | 4 | static int kObservingCollectionViewOffset; 5 | static NSString * const kContentOffsetKeyPath = @"contentOffset"; 6 | 7 | @interface MTCardLayoutHelper() 8 | 9 | @property (nonatomic, weak) UICollectionView *collectionView; 10 | 11 | @property (nonatomic, strong) UITapGestureRecognizer * tapGestureRecognizer; 12 | 13 | @end 14 | 15 | @implementation MTCardLayoutHelper 16 | 17 | - (id)initWithCollectionView:(UICollectionView *)collectionView 18 | { 19 | self = [super init]; 20 | if (self) 21 | { 22 | self.collectionView = collectionView; 23 | self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self 24 | action:@selector(handleTapGesture:)]; 25 | self.tapGestureRecognizer.delegate = self; 26 | [self.collectionView addGestureRecognizer:self.tapGestureRecognizer]; 27 | 28 | [collectionView addObserver:self 29 | forKeyPath:kContentOffsetKeyPath 30 | options:0 31 | context:&kObservingCollectionViewOffset]; 32 | 33 | } 34 | return self; 35 | } 36 | 37 | - (void)unbindFromCollectionView:(UICollectionView *)collectionView 38 | { 39 | [collectionView removeObserver:self forKeyPath:kContentOffsetKeyPath context:&kObservingCollectionViewOffset]; 40 | } 41 | 42 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 43 | { 44 | if (context == &kObservingCollectionViewOffset) { 45 | UICollectionView *collectionView = self.collectionView; 46 | if (collectionView && collectionView.dragging) 47 | { 48 | UIEdgeInsets edgeInsets = collectionView.contentInset; 49 | BOOL bounces = collectionView.bounces; 50 | 51 | if (collectionView.contentOffset.y < - 100 - edgeInsets.top && collectionView.scrollEnabled) 52 | { 53 | collectionView.contentInset = UIEdgeInsetsMake(-collectionView.contentOffset.y, edgeInsets.left, edgeInsets.bottom, edgeInsets.right); 54 | collectionView.bounces = NO; 55 | 56 | [self.collectionView setViewMode:MTCardLayoutViewModePresenting animated:YES completion:^(BOOL finished) { 57 | collectionView.contentInset = edgeInsets; 58 | collectionView.bounces = bounces; 59 | }]; 60 | } 61 | } 62 | } 63 | else { 64 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 65 | } 66 | } 67 | 68 | #pragma mark - Tap gesture 69 | 70 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 71 | { 72 | if (gestureRecognizer == self.tapGestureRecognizer) { 73 | if ([self.collectionView numberOfItemsInSection:0] == 0) { 74 | return NO; 75 | } 76 | CGPoint point = [gestureRecognizer locationInView:self.collectionView]; 77 | id delegate = (id)self.collectionView.delegate; 78 | if ([delegate respondsToSelector:@selector(collectionView:shouldRecognizeTapGestureAtPoint:)] && 79 | ![delegate collectionView:self.collectionView shouldRecognizeTapGestureAtPoint:point]) { 80 | return NO; 81 | } 82 | } 83 | 84 | return YES; 85 | } 86 | 87 | - (void)handleTapGesture:(UITapGestureRecognizer *)gestureRecognizer 88 | { 89 | if (self.viewMode == MTCardLayoutViewModePresenting) { 90 | [self.collectionView setViewMode:MTCardLayoutViewModeDefault animated:YES completion:nil]; 91 | NSArray *selectedIndexPaths = [self.collectionView indexPathsForSelectedItems]; 92 | [selectedIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath * indexPath, NSUInteger idx, BOOL *stop) { 93 | [self.collectionView deselectAndNotifyDelegate:indexPath]; 94 | }]; 95 | } else { // MTCardLayoutViewModeDefault 96 | NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:[gestureRecognizer locationInView:self.collectionView]]; 97 | if (indexPath) { 98 | [self.collectionView selectAndNotifyDelegate:indexPath]; 99 | } 100 | } 101 | } 102 | 103 | @end -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Launch.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/ViewController.m: -------------------------------------------------------------------------------- 1 | #import "ViewController.h" 2 | #import "PassCell.h" 3 | #import "MTCardLayout.h" 4 | #import "UICollectionView+CardLayout.h" 5 | #import "UICollectionView+DraggableCardLayout.h" 6 | #import "MTCardLayoutHelper.h" 7 | #import "SearchViewController.h" 8 | 9 | @interface ViewController () 10 | 11 | @property (nonatomic, strong) SearchViewController *searchViewController; 12 | @property (nonatomic, strong) NSMutableArray * items; 13 | 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | #pragma mark Status Bar color 19 | 20 | - (UIStatusBarStyle)preferredStatusBarStyle 21 | { 22 | return UIStatusBarStyleLightContent; 23 | } 24 | 25 | #pragma mark - View Lifecycle 26 | 27 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 28 | { 29 | [self.collectionView setViewMode:MTCardLayoutViewModePresenting animated:YES completion:nil]; 30 | } 31 | 32 | - (void)viewDidLoad 33 | { 34 | [super viewDidLoad]; 35 | 36 | self.items = [NSMutableArray arrayWithCapacity:20]; 37 | for (int i = 0; i < 20; i++) 38 | { 39 | [self.items addObject:[NSString stringWithFormat:@"Item %d", i]]; 40 | } 41 | 42 | self.searchViewController = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"SearchViewController"]; 43 | self.searchViewController.delegate = self; 44 | self.collectionView.backgroundView = self.searchViewController.view; 45 | } 46 | 47 | #pragma mark - UICollectionViewDatasource 48 | 49 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 50 | { 51 | return self.items.count; 52 | } 53 | 54 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 55 | { 56 | PassCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"pass" forIndexPath:indexPath]; 57 | 58 | cell.titleLabel.text = self.items[indexPath.item]; 59 | return cell; 60 | } 61 | 62 | - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath 63 | { 64 | return YES; 65 | } 66 | 67 | - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 68 | { 69 | NSString * item = self.items[fromIndexPath.item]; 70 | [self.items removeObjectAtIndex:fromIndexPath.item]; 71 | [self.items insertObject:item atIndex:toIndexPath.item]; 72 | } 73 | 74 | - (void)collectionView:(UICollectionView *)collectionView didMoveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)toIndexPath 75 | { 76 | } 77 | 78 | - (BOOL)collectionView:(UICollectionView *)collectionView canDeleteItemAtIndexPath:(NSIndexPath *)indexPath 79 | { 80 | return YES; 81 | } 82 | 83 | - (UIView *)collectionView:(UICollectionView *)collectionView deletionIndicatorViewForItemAtIndexPath:(NSIndexPath *)indexPath 84 | { 85 | NSString *imageName = (self.collectionView.viewMode == MTCardLayoutViewModeDefault) ? @"delete_small" : @"delete"; 86 | return [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]]; 87 | } 88 | 89 | - (void)collectionView:(UICollectionView *)collectionView deleteItemAtIndexPath:(NSIndexPath *)indexPath 90 | { 91 | [self.items removeObjectAtIndex:indexPath.item]; 92 | } 93 | 94 | - (void)collectionView:(UICollectionView *)collectionView didDeleteItemAtIndexPath:(NSIndexPath *)indexPath 95 | { 96 | } 97 | 98 | #pragma mark SearchCell 99 | 100 | - (void)searchControllerWillBeginSearch:(SearchViewController *)controller 101 | { 102 | if (self.collectionView.viewMode != MTCardLayoutViewModePresenting) 103 | { 104 | [self.collectionView setViewMode:MTCardLayoutViewModePresenting animated:YES completion:nil]; 105 | } 106 | } 107 | 108 | - (void)searchControllerWillEndSearch:(SearchViewController *)controller 109 | { 110 | if (self.collectionView.viewMode != MTCardLayoutViewModeDefault) 111 | { 112 | [self.collectionView setViewMode:MTCardLayoutViewModeDefault animated:YES completion:nil]; 113 | } 114 | } 115 | 116 | #pragma mark Backside 117 | 118 | - (IBAction)flip:(id)sender 119 | { 120 | PassCell *cell = (PassCell *)[self.collectionView cellForItemAtIndexPath:[[self.collectionView indexPathsForSelectedItems] firstObject]]; 121 | if (sender == cell.infoButton) 122 | { 123 | [cell flipTransitionWithOptions:UIViewAnimationOptionTransitionFlipFromLeft halfway:^(BOOL finished) { 124 | cell.infoButton.hidden = YES; 125 | cell.doneButton.hidden = NO; 126 | } completion:nil]; 127 | } 128 | else 129 | { 130 | [cell flipTransitionWithOptions:UIViewAnimationOptionTransitionFlipFromRight halfway:^(BOOL finished) { 131 | cell.infoButton.hidden = NO; 132 | cell.doneButton.hidden = YES; 133 | } completion:nil]; 134 | } 135 | } 136 | 137 | @end 138 | -------------------------------------------------------------------------------- /MTCardLayout/MTCardLayout.m: -------------------------------------------------------------------------------- 1 | #import "MTCardLayout.h" 2 | #import "UICollectionView+CardLayout.h" 3 | 4 | @interface UICollectionView (CardLayoutPrivate) 5 | - (void)cardLayoutCleanup; 6 | @end 7 | 8 | @interface MTCardLayout () 9 | 10 | @end 11 | 12 | @implementation MTCardLayout 13 | 14 | - (id)init 15 | { 16 | self = [super init]; 17 | 18 | if (self) 19 | { 20 | [self useDefaultMetricsAndInvalidate:NO]; 21 | } 22 | 23 | return self; 24 | } 25 | 26 | - (id)initWithCoder:(NSCoder *)decoder 27 | { 28 | self = [super initWithCoder:decoder]; 29 | 30 | if (self) 31 | { 32 | [self useDefaultMetricsAndInvalidate:NO]; 33 | } 34 | 35 | return self; 36 | } 37 | 38 | - (void)dealloc 39 | { 40 | [self.collectionView cardLayoutCleanup]; 41 | } 42 | 43 | #pragma mark - Initialization 44 | 45 | - (void)useDefaultMetricsAndInvalidate:(BOOL)invalidate 46 | { 47 | MTCardLayoutMetrics m; 48 | MTCardLayoutEffects e; 49 | 50 | m.presentingInsets = UIEdgeInsetsMake(00, 0, 44, 0); 51 | m.listingInsets = UIEdgeInsetsMake(20.0, 0, 0, 0); 52 | m.minimumVisibleHeight = 74; 53 | m.flexibleTop = 0.0; 54 | m.stackedVisibleHeight = 6.0; 55 | m.maxStackedCards = 5; 56 | 57 | e.inheritance = 0.10; 58 | e.sticksTop = YES; 59 | e.bouncesTop = YES; 60 | e.spreading = NO; 61 | 62 | _metrics = m; 63 | _effects = e; 64 | 65 | if (invalidate) [self invalidateLayout]; 66 | } 67 | 68 | #pragma mark - Accessors 69 | 70 | - (void)setMetrics:(MTCardLayoutMetrics)metrics 71 | { 72 | _metrics = metrics; 73 | 74 | [self invalidateLayout]; 75 | } 76 | 77 | - (void)setEffects:(MTCardLayoutEffects)effects 78 | { 79 | _effects = effects; 80 | 81 | [self invalidateLayout]; 82 | } 83 | 84 | #pragma mark - Layout 85 | 86 | - (void)prepareLayout 87 | { 88 | _metrics.visibleHeight = _metrics.minimumVisibleHeight; 89 | if (_effects.spreading) 90 | { 91 | NSInteger numberOfCards = [self.collectionView numberOfItemsInSection:0]; 92 | if (numberOfCards > 0) 93 | { 94 | CGFloat height = (self.collectionView.frame.size.height - self.collectionView.contentInset.top - _metrics.listingInsets.top - _metrics.flexibleTop) / numberOfCards; 95 | if (height > _metrics.visibleHeight) _metrics.visibleHeight = height; 96 | } 97 | } 98 | } 99 | 100 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath selectedIndexPath:(NSIndexPath *)selectedIndexPath viewMode:(MTCardLayoutViewMode)viewMode 101 | { 102 | UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 103 | attributes.zIndex = indexPath.item + 1; 104 | attributes.transform3D = CATransform3DMakeTranslation(0, 0, indexPath.item * 0.0001); 105 | 106 | if (self.collectionView.viewMode == MTCardLayoutViewModePresenting) 107 | { 108 | if (selectedIndexPath && [selectedIndexPath isEqual:indexPath]) 109 | { 110 | // Layout selected cell (normal size) 111 | attributes.frame = frameForSelectedCard(self.collectionView.bounds, self.collectionView.contentInset, _metrics); 112 | } 113 | else 114 | { 115 | // Layout unselected cell (bottom-stuck) 116 | attributes.frame = frameForUnselectedCard(indexPath, selectedIndexPath, self.collectionView.bounds, _metrics); 117 | } 118 | } 119 | else // stack mode 120 | { 121 | // Layout collapsed cells (collapsed size) 122 | attributes.frame = frameForCardAtIndex(indexPath, self.collectionView.bounds, self.collectionView.contentInset, _metrics, _effects); 123 | } 124 | 125 | attributes.hidden = attributes.frame.size.height == 0; 126 | 127 | return attributes; 128 | } 129 | 130 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 131 | { 132 | NSArray *selectedIndexPaths = [self.collectionView indexPathsForSelectedItems]; 133 | return [self layoutAttributesForItemAtIndexPath:indexPath selectedIndexPath:[selectedIndexPaths firstObject] viewMode:self.collectionView.viewMode]; 134 | } 135 | 136 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 137 | { 138 | CGRect effectiveBounds = self.collectionView.bounds; 139 | effectiveBounds.origin.y += self.collectionView.contentInset.top; 140 | effectiveBounds.origin.y += _metrics.listingInsets.top; 141 | effectiveBounds.size.height -= _metrics.listingInsets.top + _metrics.listingInsets.bottom; 142 | rect = CGRectIntersection(rect, effectiveBounds); 143 | 144 | NSInteger numberOfItems = [self numberOfItemsInCollectionViewSection:0]; 145 | 146 | NSRange range = rangeForVisibleCells(rect, numberOfItems, _metrics); 147 | 148 | NSMutableArray *cells = [NSMutableArray arrayWithCapacity:range.length + 2]; 149 | 150 | NSIndexPath *selectedIndexPath = [[self.collectionView indexPathsForSelectedItems] firstObject]; 151 | 152 | for (NSUInteger item=range.location; item < (range.location + range.length); item++) 153 | { 154 | [cells addObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] selectedIndexPath:selectedIndexPath viewMode:self.collectionView.viewMode]]; 155 | } 156 | 157 | // selected item is out of range 158 | if (self.collectionView.viewMode == MTCardLayoutViewModePresenting && selectedIndexPath && (selectedIndexPath.item < range.location || selectedIndexPath.item >= range.location + range.length)) 159 | { 160 | [cells addObject:[self layoutAttributesForItemAtIndexPath:selectedIndexPath selectedIndexPath:selectedIndexPath viewMode:self.collectionView.viewMode]]; 161 | } 162 | 163 | return cells; 164 | } 165 | 166 | - (CGSize)collectionViewContentSize 167 | { 168 | return collectionViewSize(self.collectionView.bounds, 169 | self.collectionView.contentInset, 170 | [self numberOfItemsInCollectionViewSection:0], 171 | _metrics); 172 | } 173 | 174 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 175 | { 176 | return YES; 177 | } 178 | 179 | #pragma mark - Postioning 180 | 181 | - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity 182 | { 183 | CGPoint targetContentOffset = proposedContentOffset; 184 | 185 | if (self.collectionView.scrollEnabled) 186 | { 187 | targetContentOffset.y += self.collectionView.contentInset.top; 188 | CGFloat flexibleHeight = _metrics.flexibleTop; 189 | if (targetContentOffset.y < flexibleHeight) { 190 | // Snap to either 0 or flexibleHeight offset when the position is somewhere in between 191 | targetContentOffset.y = (targetContentOffset.y < flexibleHeight / 2) ? 0.0 : flexibleHeight; 192 | } else { 193 | if (_metrics.visibleHeight > 0) { 194 | targetContentOffset.y = roundf((targetContentOffset.y - flexibleHeight) / _metrics.visibleHeight) * _metrics.visibleHeight + flexibleHeight; 195 | } 196 | } 197 | targetContentOffset.y -= self.collectionView.contentInset.top; 198 | } 199 | 200 | 201 | return targetContentOffset; 202 | } 203 | 204 | #pragma mark Cell visibility 205 | 206 | NSRange rangeForVisibleCells(CGRect rect, NSInteger count, MTCardLayoutMetrics m) 207 | { 208 | rect.origin.y -= m.flexibleTop + m.listingInsets.top; 209 | NSInteger min = (m.visibleHeight == 0) ? 0 : floor(rect.origin.y / m.visibleHeight); 210 | NSInteger max = (m.visibleHeight == 0) ? count : ceil((rect.origin.y + rect.size.height) / m.visibleHeight); 211 | 212 | max = MIN(max, count); 213 | 214 | min = (min < 0) ? 0 : min; 215 | min = (min < max) ? min : max; 216 | 217 | NSRange r = NSMakeRange(min, max-min); 218 | 219 | return r; 220 | } 221 | 222 | CGSize collectionViewSize(CGRect bounds, UIEdgeInsets contentInset, NSInteger count, MTCardLayoutMetrics m) 223 | { 224 | CGFloat height = count * m.visibleHeight + m.flexibleTop + m.listingInsets.top + fmodf(bounds.size.height - contentInset.top - m.listingInsets.top, m.visibleHeight); 225 | return CGSizeMake(bounds.size.width, height); 226 | } 227 | 228 | #pragma mark Cell positioning 229 | 230 | /// Normal collapsed cell, with bouncy animations on top 231 | CGRect frameForCardAtIndex(NSIndexPath *indexPath, CGRect b, UIEdgeInsets contentInset, MTCardLayoutMetrics m, MTCardLayoutEffects e) 232 | { 233 | CGRect f = UIEdgeInsetsInsetRect(UIEdgeInsetsInsetRect(b, contentInset), m.presentingInsets); 234 | 235 | f.origin.y = indexPath.item * m.visibleHeight + m.flexibleTop + m.listingInsets.top; 236 | 237 | if (b.origin.y + contentInset.top < 0 && e.inheritance > 0.0 && e.bouncesTop) 238 | { 239 | // Bouncy effect on top (works only on constant invalidation) 240 | if (indexPath.section == 0 && indexPath.item == 0) 241 | { 242 | // Keep stuck at top 243 | f.origin.y = (b.origin.y + contentInset.top) * e.inheritance/2.0 + m.flexibleTop + m.listingInsets.top; 244 | } 245 | else 246 | { 247 | // Displace in stepping amounts factored by resitatnce 248 | f.origin.y -= (b.origin.y + contentInset.top) * indexPath.item * e.inheritance; 249 | } 250 | } 251 | else if (b.origin.y + contentInset.top > 0) 252 | { 253 | // Stick to top 254 | if (f.origin.y < b.origin.y + contentInset.top + m.listingInsets.top && e.sticksTop) 255 | { 256 | f.origin.y = b.origin.y + contentInset.top + m.listingInsets.top; 257 | } 258 | } 259 | 260 | return f; 261 | } 262 | 263 | CGRect frameForSelectedCard(CGRect b, UIEdgeInsets contentInset, MTCardLayoutMetrics m) 264 | { 265 | return UIEdgeInsetsInsetRect(UIEdgeInsetsInsetRect(b, contentInset), m.presentingInsets); 266 | } 267 | 268 | /// Bottom-stack card 269 | CGRect frameForUnselectedCard(NSIndexPath *indexPath, NSIndexPath *indexPathSelected, CGRect b, MTCardLayoutMetrics m) 270 | { 271 | NSInteger firstVisibleItem = ceil((b.origin.y - m.flexibleTop - m.listingInsets.top) / m.visibleHeight); 272 | if (firstVisibleItem < 0) firstVisibleItem = 0; 273 | 274 | NSInteger itemOrder = indexPath.item - firstVisibleItem; 275 | if (indexPathSelected && indexPath.item > indexPathSelected.item) itemOrder--; 276 | 277 | CGFloat bottomStackedTotalHeight = m.stackedVisibleHeight * m.maxStackedCards; 278 | 279 | CGRect f = UIEdgeInsetsInsetRect(b, m.presentingInsets); 280 | f.origin.y = b.origin.y + b.size.height + m.stackedVisibleHeight * itemOrder - bottomStackedTotalHeight; 281 | if (indexPath.item < firstVisibleItem) 282 | { 283 | f.size.height = 0; 284 | } 285 | 286 | // Off screen card may be 287 | if (f.origin.y >= CGRectGetMaxY(b)) { 288 | f.origin.y = CGRectGetMaxY(b) - 0.5; 289 | } 290 | 291 | return f; 292 | } 293 | 294 | /** 295 | Returns the number of items in the given section. If the section does not 296 | exist, returns 0 instead of throwing an exception. 297 | */ 298 | - (NSInteger)numberOfItemsInCollectionViewSection:(NSInteger)section { 299 | return 300 | [self.collectionView numberOfSections] > section ? 301 | [self.collectionView numberOfItemsInSection:section] : 302 | 0; 303 | } 304 | 305 | @end 306 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 52 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /MTCardLayout/MTDraggableCardLayoutHelper.m: -------------------------------------------------------------------------------- 1 | #import "MTDraggableCardLayoutHelper.h" 2 | #import "MTCommonTypes.h" 3 | #import "UICollectionView+CardLayout.h" 4 | #import "UICollectionView+DraggableCardLayout.h" 5 | #import "MTDraggableCardLayout.h" 6 | 7 | #define DEFAULT_ANIMATION_DURATION 0.25 8 | 9 | #define SCROLLING_SPEED 300.f 10 | #define SCROLLING_EDGE_INSET 50.f 11 | 12 | #define DRAG_ACTION_LIMIT 150.0 13 | 14 | typedef NS_ENUM(NSInteger, MTScrollingDirection) { 15 | MTScrollingDirectionUnknown = 0, 16 | MTScrollingDirectionUp, 17 | MTScrollingDirectionDown, 18 | }; 19 | 20 | typedef NS_ENUM(NSInteger, MTDraggingAction) { 21 | MTDraggingActionNone, 22 | MTDraggingActionReorder, 23 | MTDraggingActionSwipeToDelete, 24 | MTDraggingActionDismissPresenting, 25 | }; 26 | 27 | @interface MTDraggableCardLayoutHelper() 28 | 29 | @property (nonatomic, weak) UICollectionView *collectionView; 30 | 31 | @property (nonatomic) MTDraggingAction draggingAction; 32 | @property (nonatomic, copy) UICollectionViewLayoutAttributes *movingItemAttributes; 33 | @property (nonatomic, strong) NSIndexPath *toIndexPath; 34 | @property (nonatomic) CGPoint movingItemTranslation; 35 | 36 | @property (nonatomic, strong) UILongPressGestureRecognizer *longPressGestureRecognizer; 37 | @property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer; 38 | 39 | @property (nonatomic, strong) CADisplayLink *scrollTimer; 40 | @property (nonatomic) MTScrollingDirection scrollingDirection; 41 | 42 | @property (nonatomic, strong) UIView *deletionIndicatorView; 43 | 44 | @end 45 | 46 | @implementation MTDraggableCardLayoutHelper 47 | 48 | - (id)initWithCollectionView:(UICollectionView *)collectionView 49 | { 50 | self = [super init]; 51 | if (self) { 52 | self.collectionView = collectionView; 53 | 54 | self.longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] 55 | initWithTarget:self 56 | action:@selector(handleLongPressGesture:)]; 57 | self.longPressGestureRecognizer.delegate = self; 58 | [self.collectionView addGestureRecognizer:self.longPressGestureRecognizer]; 59 | 60 | self.panGestureRecognizer = [[UIPanGestureRecognizer alloc] 61 | initWithTarget:self 62 | action:@selector(handlePanGesture:)]; 63 | self.panGestureRecognizer.maximumNumberOfTouches = 1; 64 | self.panGestureRecognizer.delegate = self; 65 | [self.collectionView addGestureRecognizer:self.panGestureRecognizer]; 66 | } 67 | return self; 68 | } 69 | 70 | - (NSIndexPath *)indexPathForItemClosestToRect:(CGRect)frame 71 | { 72 | NSArray *layoutAttrsInRect; 73 | NSInteger closestDist = NSIntegerMax; 74 | NSIndexPath *indexPath; 75 | NSIndexPath *toIndexPath = self.toIndexPath; 76 | 77 | // We need original positions of cells 78 | self.toIndexPath = nil; 79 | layoutAttrsInRect = [self.collectionView.collectionViewLayout 80 | layoutAttributesForElementsInRect:self.collectionView.bounds]; 81 | self.toIndexPath = toIndexPath; 82 | 83 | CGPoint point = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)); 84 | 85 | // What cell are we closest to? 86 | for (UICollectionViewLayoutAttributes *layoutAttr in layoutAttrsInRect) { 87 | if (layoutAttr.representedElementCategory == UICollectionElementCategoryCell) { 88 | CGFloat xd = layoutAttr.center.x - point.x; 89 | CGFloat yd = layoutAttr.center.y - point.y; 90 | NSInteger dist = sqrtf(xd*xd + yd*yd); 91 | if (dist < closestDist) { 92 | closestDist = dist; 93 | indexPath = layoutAttr.indexPath; 94 | } 95 | } 96 | } 97 | 98 | return indexPath; 99 | } 100 | 101 | - (CGRect)movingItemFrame 102 | { 103 | return CGRectOffset(self.movingItemAttributes.frame, self.movingItemTranslation.x, self.movingItemTranslation.y); 104 | } 105 | 106 | - (CGFloat)movingItemAlpha 107 | { 108 | CGRect frame = self.movingItemAttributes.frame; 109 | CGPoint translation = self.movingItemTranslation; 110 | if (frame.size.width == 0 || frame.size.height == 0) return 1.0; 111 | 112 | CGFloat alphaH = translation.x > -DRAG_ACTION_LIMIT ? 1.0 : MAX(0.0, (frame.size.width + translation.x + DRAG_ACTION_LIMIT) / frame.size.width - 0.1); 113 | CGFloat alphaV = translation.y > -DRAG_ACTION_LIMIT ? 1.0 : MAX(0.0, (frame.size.height + translation.y + DRAG_ACTION_LIMIT) / frame.size.height - 0.1); 114 | 115 | return MIN(alphaH, alphaV); 116 | } 117 | 118 | - (void)updateMovingCell 119 | { 120 | NSIndexPath *indexPath = self.movingItemAttributes.indexPath; 121 | NSAssert(indexPath, @"movingItemAttributes cannot be nil"); 122 | 123 | UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 124 | if (!cell) return; // Cell is not visible 125 | 126 | cell.frame = self.movingItemFrame; 127 | cell.alpha = self.movingItemAlpha; 128 | } 129 | 130 | - (void)clearDraggingAction 131 | { 132 | self.movingItemAttributes = nil; 133 | self.toIndexPath = nil; 134 | self.movingItemTranslation = CGPointZero; 135 | self.draggingAction = MTDraggingActionNone; 136 | } 137 | 138 | #pragma mark - Moving/Scrolling 139 | 140 | - (BOOL)canMoveItemAtIndexPath:(NSIndexPath *)indexPath 141 | { 142 | id dataSource = (id)self.collectionView.dataSource; 143 | 144 | return [dataSource respondsToSelector:@selector(collectionView:canMoveItemAtIndexPath:)] && 145 | [dataSource collectionView:self.collectionView canMoveItemAtIndexPath:indexPath]; 146 | } 147 | 148 | - (void)invalidatesScrollTimer 149 | { 150 | if (self.scrollTimer != nil) { 151 | [self.scrollTimer invalidate]; 152 | self.scrollTimer = nil; 153 | } 154 | self.scrollingDirection = MTScrollingDirectionUnknown; 155 | } 156 | 157 | - (void)setupScrollTimerInDirection:(MTScrollingDirection)direction 158 | { 159 | self.scrollingDirection = direction; 160 | if (self.scrollTimer == nil) { 161 | self.scrollTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleScroll:)]; 162 | [self.scrollTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 163 | } 164 | } 165 | 166 | - (void)handleScroll:(NSTimer *)timer 167 | { 168 | CGSize frameSize = self.collectionView.bounds.size; 169 | CGSize contentSize = self.collectionView.contentSize; 170 | CGPoint contentOffset = self.collectionView.contentOffset; 171 | CGFloat distance = SCROLLING_SPEED / 60.f; 172 | 173 | switch (self.scrollingDirection) { 174 | case MTScrollingDirectionUp: { 175 | distance = -distance; 176 | if ((contentOffset.y + distance) <= 0.f) { 177 | distance = -contentOffset.y; 178 | } 179 | } break; 180 | case MTScrollingDirectionDown: { 181 | CGFloat maxY = MAX(contentSize.height, frameSize.height) - frameSize.height; 182 | if ((contentOffset.y + distance) >= maxY) { 183 | distance = maxY - contentOffset.y; 184 | } 185 | } break; 186 | default: break; 187 | } 188 | 189 | contentOffset.y += distance; 190 | self.collectionView.contentOffset = contentOffset; 191 | self.movingItemTranslation = CGPointMake(0, [self.panGestureRecognizer translationInView:self.collectionView].y + distance); 192 | // Reset in the gesture as well 193 | [self.panGestureRecognizer setTranslation:self.movingItemTranslation inView:self.collectionView]; 194 | 195 | // Warp items while scrolling 196 | NSIndexPath *indexPath = [self indexPathForItemClosestToRect:[self movingItemFrame]]; 197 | [self warpToIndexPath:indexPath]; 198 | } 199 | 200 | - (void)warpToIndexPath:(NSIndexPath *)indexPath 201 | { 202 | BOOL validIndexPath = YES; 203 | 204 | id dataSource = (id)self.collectionView.dataSource; 205 | 206 | if ([dataSource respondsToSelector:@selector(collectionView:canMoveItemAtIndexPath:toIndexPath:)] == YES 207 | && [dataSource 208 | collectionView:self.collectionView 209 | canMoveItemAtIndexPath:self.movingItemAttributes.indexPath 210 | toIndexPath:indexPath] == NO) { 211 | validIndexPath = NO; 212 | } 213 | 214 | [self.collectionView performBatchUpdates:^{ 215 | if (validIndexPath) { 216 | self.toIndexPath = indexPath; 217 | } 218 | } completion:nil]; 219 | } 220 | 221 | #pragma mark - Deleting 222 | 223 | - (BOOL)canDeleteItemAtIndexPath:(NSIndexPath *)indexPath 224 | { 225 | id dataSource = (id)self.collectionView.dataSource; 226 | 227 | return [dataSource respondsToSelector:@selector(collectionView:canDeleteItemAtIndexPath:)] && 228 | [dataSource collectionView:self.collectionView canDeleteItemAtIndexPath:indexPath]; 229 | } 230 | 231 | - (void)showDeletionIndicatorAnimated:(BOOL)animated 232 | { 233 | UICollectionView *collectionView = self.collectionView; 234 | NSIndexPath *indexPath = self.movingItemAttributes.indexPath; 235 | NSAssert(indexPath, @"movingItemAttributes cannot be nil"); 236 | 237 | if (self.deletionIndicatorView) { 238 | [self.deletionIndicatorView removeFromSuperview]; 239 | self.deletionIndicatorView = nil; 240 | } 241 | 242 | id delegate = (id)collectionView.delegate; 243 | if ([delegate respondsToSelector:@selector(collectionView:deletionIndicatorViewForItemAtIndexPath:)]) { 244 | self.deletionIndicatorView = [delegate collectionView:collectionView deletionIndicatorViewForItemAtIndexPath:indexPath]; 245 | if (self.deletionIndicatorView) { 246 | CGPoint center; 247 | if (collectionView.viewMode == MTCardLayoutViewModePresenting) { 248 | center = CGPointMake(CGRectGetMidX(collectionView.bounds), CGRectGetMidY(collectionView.bounds)); 249 | } else { 250 | CGRect frame = self.movingItemAttributes.frame; 251 | MTDraggableCardLayout *cardLayout = (MTDraggableCardLayout *)collectionView.collectionViewLayout; 252 | NSAssert([cardLayout isKindOfClass:[MTDraggableCardLayout class]], @"Collection layout is not of correct class"); 253 | frame.size.height = cardLayout.metrics.minimumVisibleHeight; 254 | CGFloat padding = (frame.size.height - self.deletionIndicatorView.frame.size.height) / 2; 255 | center = CGPointMake(padding + self.deletionIndicatorView.frame.size.width / 2, CGRectGetMidY(frame)); 256 | } 257 | 258 | self.deletionIndicatorView.center = center; 259 | self.deletionIndicatorView.layer.transform = CATransform3DMakeTranslation(0, 0, 1); 260 | [collectionView addSubview:self.deletionIndicatorView]; 261 | if (animated) { 262 | self.deletionIndicatorView.layer.transform = CATransform3DScale(self.deletionIndicatorView.layer.transform, 0.1, 0.1, 0.1); 263 | [UIView animateWithDuration:DEFAULT_ANIMATION_DURATION animations:^{ 264 | self.deletionIndicatorView.layer.transform = CATransform3DMakeTranslation(0, 0, 1); 265 | }]; 266 | } 267 | } 268 | } 269 | } 270 | 271 | - (void)updateDeletionIndicator 272 | { 273 | if (self.deletionIndicatorView) { 274 | CGFloat offset = MIN(self.movingItemTranslation.x, self.movingItemTranslation.y); // expecting negative value 275 | self.deletionIndicatorView.alpha = MAX(0.0, MIN(1.0, -offset/DRAG_ACTION_LIMIT)); 276 | if ([self.deletionIndicatorView isKindOfClass:[UIImageView class]]) { 277 | ((UIImageView *)self.deletionIndicatorView).highlighted = offset < -DRAG_ACTION_LIMIT; 278 | } 279 | } 280 | } 281 | 282 | - (void)hideDeletionIndicatorViewAnimated:(BOOL)animated 283 | { 284 | if (animated) { 285 | [UIView animateWithDuration:DEFAULT_ANIMATION_DURATION animations:^{ 286 | self.deletionIndicatorView.alpha = 0.0; 287 | } completion:^(BOOL finished) { 288 | [self.deletionIndicatorView removeFromSuperview]; 289 | self.deletionIndicatorView.alpha = 1.0; 290 | self.deletionIndicatorView = nil; 291 | }]; 292 | } else { 293 | [self.deletionIndicatorView removeFromSuperview]; 294 | self.deletionIndicatorView = nil; 295 | } 296 | } 297 | 298 | 299 | - (void)confirmDeletingItemAtIndexPath:(NSIndexPath *)indexPath completion:(void(^)(BOOL cancel))completion 300 | { 301 | id delegate = (id)self.collectionView.delegate; 302 | if ([delegate respondsToSelector:@selector(collectionView:willDeleteItemAtIndexPath:completion:)]) { 303 | [delegate collectionView:self.collectionView willDeleteItemAtIndexPath:indexPath completion:completion]; 304 | } else { 305 | completion(NO); 306 | } 307 | } 308 | 309 | - (void)finalizeDeletingItemWithSwipeDirection:(UISwipeGestureRecognizerDirection)direction 310 | { 311 | UICollectionView *collectionView = self.collectionView; 312 | NSIndexPath *indexPath = self.movingItemAttributes.indexPath; 313 | NSAssert(indexPath, @"movingItemAttributes cannot be nil"); 314 | 315 | CGRect originalFrame = self.movingItemAttributes.frame; 316 | 317 | [UIView animateWithDuration:DEFAULT_ANIMATION_DURATION animations:^{ 318 | // Continue to move item off screen 319 | CGPoint translation = CGPointZero; 320 | switch (direction) { 321 | case UISwipeGestureRecognizerDirectionLeft: 322 | translation.x = -originalFrame.size.width + 1; 323 | break; 324 | case UISwipeGestureRecognizerDirectionUp: 325 | translation.y = -originalFrame.size.height + 1; 326 | break; 327 | case UISwipeGestureRecognizerDirectionRight: 328 | translation.x = originalFrame.size.width - 1; 329 | break; 330 | case UISwipeGestureRecognizerDirectionDown: 331 | translation.y = originalFrame.size.height - 1; 332 | break; 333 | } 334 | self.movingItemTranslation = translation; 335 | [self updateMovingCell]; 336 | 337 | collectionView.userInteractionEnabled = NO; 338 | [self confirmDeletingItemAtIndexPath:indexPath completion:^(BOOL cancel) { 339 | collectionView.userInteractionEnabled = YES; 340 | 341 | if (cancel) { 342 | self.movingItemTranslation = CGPointZero; 343 | [UIView animateWithDuration:DEFAULT_ANIMATION_DURATION animations:^{ 344 | [self updateMovingCell]; 345 | }]; 346 | } else { 347 | id dataSource = (id)self.collectionView.dataSource; 348 | id delegate = (id)self.collectionView.delegate; 349 | 350 | [collectionView performBatchUpdates:^{ 351 | [collectionView deselectItemAtIndexPath:indexPath animated:NO]; 352 | [dataSource collectionView:collectionView deleteItemAtIndexPath:indexPath]; 353 | [collectionView deleteItemsAtIndexPaths:@[indexPath]]; 354 | } completion:^(BOOL finished) { 355 | if ([delegate respondsToSelector:@selector(collectionView:didDeleteItemAtIndexPath:)]) { 356 | [delegate collectionView:self.collectionView didDeleteItemAtIndexPath:indexPath]; 357 | } 358 | }]; 359 | } 360 | 361 | [self clearDraggingAction]; 362 | }]; 363 | } completion:nil]; 364 | } 365 | 366 | #pragma mark - Gesture Recognizers 367 | 368 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 369 | { 370 | CGPoint point = [gestureRecognizer locationInView:self.collectionView]; 371 | NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point]; 372 | 373 | if (!indexPath) return NO; 374 | 375 | if (gestureRecognizer == self.longPressGestureRecognizer) { 376 | return self.collectionView.viewMode == MTCardLayoutViewModeDefault; 377 | } 378 | 379 | if (gestureRecognizer == self.panGestureRecognizer) { 380 | id delegate = (id)self.collectionView.delegate; 381 | if ([delegate respondsToSelector:@selector(collectionView:shouldRecognizePanGestureAtPoint:)] && 382 | ![delegate collectionView:self.collectionView shouldRecognizePanGestureAtPoint:point]) { 383 | return NO; 384 | } 385 | 386 | if (self.collectionView.viewMode == MTCardLayoutViewModeDefault && !self.movingItemAttributes) { 387 | CGPoint velocity = [self.panGestureRecognizer velocityInView:self.collectionView]; 388 | if (fabs(velocity.x) < fabs(velocity.y)) { 389 | return NO; 390 | } 391 | } 392 | 393 | return YES; 394 | } 395 | 396 | return YES; 397 | } 398 | 399 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 400 | { 401 | if ((gestureRecognizer == self.panGestureRecognizer && otherGestureRecognizer == self.longPressGestureRecognizer) || 402 | (gestureRecognizer == self.longPressGestureRecognizer && otherGestureRecognizer == self.panGestureRecognizer)) { 403 | return YES; 404 | } 405 | return NO; 406 | } 407 | 408 | - (void)handleLongPressGesture:(UILongPressGestureRecognizer *)gestureRecognizer 409 | { 410 | UICollectionView *collectionView = self.collectionView; 411 | id dataSource = (id)collectionView.dataSource; 412 | id delegate = (id)collectionView.delegate; 413 | 414 | if (collectionView.viewMode != MTCardLayoutViewModeDefault) { 415 | return; 416 | } 417 | 418 | if (gestureRecognizer.state == UIGestureRecognizerStateChanged) { 419 | return; 420 | } 421 | 422 | switch (gestureRecognizer.state) { 423 | case UIGestureRecognizerStateBegan: { 424 | if (self.draggingAction != MTDraggingActionNone) { 425 | return; 426 | } 427 | NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:[gestureRecognizer locationInView:collectionView]]; 428 | if (indexPath == nil) { 429 | return; 430 | } 431 | 432 | if (![self canMoveItemAtIndexPath:indexPath]) { 433 | return; 434 | } 435 | 436 | // Start reordering item 437 | self.draggingAction = MTDraggingActionReorder; 438 | self.movingItemAttributes = [collectionView layoutAttributesForItemAtIndexPath:indexPath]; 439 | self.toIndexPath = indexPath; 440 | self.movingItemTranslation = CGPointZero; 441 | [collectionView performBatchUpdates:nil completion:nil]; 442 | } break; 443 | 444 | case UIGestureRecognizerStateEnded: 445 | case UIGestureRecognizerStateCancelled: { 446 | if (self.draggingAction != MTDraggingActionReorder) { 447 | return; 448 | } 449 | 450 | // Need these for later, but need to nil out layoutHelper's references sooner 451 | NSIndexPath *fromIndexPath = self.movingItemAttributes.indexPath; 452 | NSAssert(fromIndexPath, @"movingItemAttributes cannot be nil"); 453 | NSIndexPath *toIndexPath = self.toIndexPath; 454 | NSAssert(toIndexPath, @"toIndexPath cannot be nil"); 455 | 456 | // Move the item 457 | [collectionView performBatchUpdates:^{ 458 | [dataSource collectionView:collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; 459 | [collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; 460 | [self clearDraggingAction]; 461 | } completion:^(BOOL finished) { 462 | if ([delegate respondsToSelector:@selector(collectionView:didMoveItemAtIndexPath:toIndexPath:)]) { 463 | [delegate collectionView:collectionView didMoveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; 464 | } 465 | }]; 466 | 467 | [self invalidatesScrollTimer]; 468 | } break; 469 | 470 | default: break; 471 | } 472 | } 473 | 474 | - (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer 475 | { 476 | UICollectionView *collectionView = self.collectionView; 477 | 478 | if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { 479 | if (self.draggingAction == MTDraggingActionNone) { 480 | NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:[gestureRecognizer locationInView:collectionView]]; 481 | if (indexPath == nil) { 482 | return; 483 | } 484 | if (collectionView.viewMode == MTCardLayoutViewModePresenting) { 485 | if (![indexPath isEqual:[[collectionView indexPathsForSelectedItems] firstObject]]) { 486 | return; 487 | } 488 | self.draggingAction = MTDraggingActionDismissPresenting; 489 | self.movingItemAttributes = [collectionView layoutAttributesForItemAtIndexPath:indexPath]; 490 | [self showDeletionIndicatorAnimated:YES]; 491 | } else if ([self canDeleteItemAtIndexPath:indexPath]) { 492 | self.draggingAction = MTDraggingActionSwipeToDelete; 493 | self.movingItemAttributes = [collectionView layoutAttributesForItemAtIndexPath:indexPath]; 494 | [self showDeletionIndicatorAnimated:YES]; 495 | } 496 | } 497 | } 498 | 499 | if (self.draggingAction == MTDraggingActionNone) { 500 | return; 501 | } 502 | 503 | NSIndexPath *indexPath = self.movingItemAttributes.indexPath; 504 | NSAssert(indexPath, @"movingItemAttributes cannot be nil"); 505 | 506 | if (gestureRecognizer.state == UIGestureRecognizerStateChanged) { 507 | switch (self.draggingAction) { 508 | case MTDraggingActionDismissPresenting: { 509 | self.movingItemTranslation = CGPointMake(0, [gestureRecognizer translationInView:collectionView].y); 510 | [self updateMovingCell]; 511 | [self updateDeletionIndicator]; 512 | 513 | if (self.movingItemTranslation.y >= DRAG_ACTION_LIMIT) 514 | { 515 | [collectionView deselectAndNotifyDelegate:indexPath]; 516 | [self clearDraggingAction]; 517 | [collectionView performBatchUpdates:nil completion:nil]; 518 | } 519 | } break; 520 | 521 | case MTDraggingActionSwipeToDelete: { 522 | self.movingItemTranslation = CGPointMake(MIN(0, [gestureRecognizer translationInView:collectionView].x), 0); 523 | [self updateMovingCell]; 524 | [self updateDeletionIndicator]; 525 | } break; 526 | 527 | case MTDraggingActionReorder: { 528 | self.movingItemTranslation = CGPointMake(0, [gestureRecognizer translationInView:self.collectionView].y); 529 | CGPoint touchLocation = [gestureRecognizer locationInView:self.collectionView]; 530 | CGRect frame = [self movingItemFrame]; 531 | 532 | if (frame.origin.y < CGRectGetMinY(self.collectionView.bounds) + SCROLLING_EDGE_INSET) { 533 | [self setupScrollTimerInDirection:MTScrollingDirectionUp]; 534 | } 535 | else if (touchLocation.y > (CGRectGetMaxY(self.collectionView.bounds) - SCROLLING_EDGE_INSET)) { 536 | [self setupScrollTimerInDirection:MTScrollingDirectionDown]; 537 | } 538 | else { 539 | [self invalidatesScrollTimer]; 540 | } 541 | 542 | // Avoid warping a second time while scrolling 543 | if (self.scrollingDirection > MTScrollingDirectionUnknown) { 544 | return; 545 | } 546 | 547 | // Warp item to finger location 548 | NSIndexPath *indexPath = [self indexPathForItemClosestToRect:frame]; 549 | [self warpToIndexPath:indexPath]; 550 | 551 | } break; 552 | 553 | default: 554 | break; 555 | } 556 | } 557 | 558 | if (gestureRecognizer.state == UIGestureRecognizerStateEnded | gestureRecognizer.state == UIGestureRecognizerStateCancelled) { 559 | if (self.draggingAction == MTDraggingActionSwipeToDelete || self.draggingAction == MTDraggingActionDismissPresenting) 560 | { 561 | NSIndexPath *indexPath = self.movingItemAttributes.indexPath; 562 | NSAssert(indexPath, @"movingItemAttributes cannot be nil"); 563 | CGPoint translation = self.movingItemTranslation; 564 | 565 | if (gestureRecognizer.state != UIGestureRecognizerStateCancelled && 566 | (translation.y < -DRAG_ACTION_LIMIT || translation.x < -DRAG_ACTION_LIMIT) && 567 | [self canDeleteItemAtIndexPath:indexPath]) { 568 | UISwipeGestureRecognizerDirection direction = (self.draggingAction == MTDraggingActionSwipeToDelete) ? 569 | UISwipeGestureRecognizerDirectionLeft : UISwipeGestureRecognizerDirectionUp; 570 | [self finalizeDeletingItemWithSwipeDirection:direction]; 571 | } else { 572 | // Return item to original position 573 | self.movingItemTranslation = CGPointZero; 574 | [UIView animateWithDuration:DEFAULT_ANIMATION_DURATION animations:^{ 575 | [self updateMovingCell]; 576 | } completion:nil]; 577 | [self clearDraggingAction]; 578 | } 579 | [self hideDeletionIndicatorViewAnimated:YES]; 580 | } 581 | } 582 | } 583 | 584 | @end 585 | -------------------------------------------------------------------------------- /Samples/Passbooks/Passbooks.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 364AA2F018C55EBD00EC2140 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 364AA2EF18C55EBD00EC2140 /* Foundation.framework */; }; 11 | 364AA2F218C55EBD00EC2140 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 364AA2F118C55EBD00EC2140 /* CoreGraphics.framework */; }; 12 | 364AA2F418C55EBD00EC2140 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 364AA2F318C55EBD00EC2140 /* UIKit.framework */; }; 13 | 364AA2FA18C55EBD00EC2140 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 364AA2F818C55EBD00EC2140 /* InfoPlist.strings */; }; 14 | 364AA2FC18C55EBD00EC2140 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 364AA2FB18C55EBD00EC2140 /* main.m */; }; 15 | 364AA30018C55EBD00EC2140 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 364AA2FF18C55EBD00EC2140 /* AppDelegate.m */; }; 16 | 364AA30318C55EBD00EC2140 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 364AA30118C55EBD00EC2140 /* Main.storyboard */; }; 17 | 364AA30618C55EBD00EC2140 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 364AA30518C55EBD00EC2140 /* ViewController.m */; }; 18 | 364AA30818C55EBD00EC2140 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 364AA30718C55EBD00EC2140 /* Images.xcassets */; }; 19 | 364AA30F18C55EBD00EC2140 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 364AA30E18C55EBD00EC2140 /* XCTest.framework */; }; 20 | 364AA31018C55EBD00EC2140 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 364AA2EF18C55EBD00EC2140 /* Foundation.framework */; }; 21 | 364AA31118C55EBD00EC2140 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 364AA2F318C55EBD00EC2140 /* UIKit.framework */; }; 22 | 364AA31918C55EBD00EC2140 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 364AA31718C55EBD00EC2140 /* InfoPlist.strings */; }; 23 | 364AA31B18C55EBD00EC2140 /* PassbooksTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 364AA31A18C55EBD00EC2140 /* PassbooksTests.m */; }; 24 | 364AA32B18C5606600EC2140 /* PassCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 364AA32A18C5606600EC2140 /* PassCell.m */; }; 25 | BA0255F61CB0F259002EE790 /* MTCardLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = BA0255EB1CB0F259002EE790 /* MTCardLayout.m */; }; 26 | BA0255F71CB0F259002EE790 /* MTCardLayoutHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = BA0255ED1CB0F259002EE790 /* MTCardLayoutHelper.m */; }; 27 | BA0255F81CB0F259002EE790 /* MTDraggableCardLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = BA0255EF1CB0F259002EE790 /* MTDraggableCardLayout.m */; }; 28 | BA0255FA1CB0F259002EE790 /* UICollectionView+CardLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = BA0255F31CB0F259002EE790 /* UICollectionView+CardLayout.m */; }; 29 | BA0255FB1CB0F259002EE790 /* UICollectionView+DraggableCardLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = BA0255F51CB0F259002EE790 /* UICollectionView+DraggableCardLayout.m */; }; 30 | BA17D530196CF6A80054D9EC /* SearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BA17D52F196CF6A80054D9EC /* SearchViewController.m */; }; 31 | BA2A4FEA196E71760074E49E /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA2A4FE9196E71760074E49E /* QuartzCore.framework */; }; 32 | BAA882501CBA388C00CA8035 /* MTDraggableCardLayoutHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = BAA8824F1CBA388C00CA8035 /* MTDraggableCardLayoutHelper.m */; }; 33 | C15AE6921CB254AB00640CD3 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C15AE6911CB254AB00640CD3 /* Launch.storyboard */; }; 34 | /* End PBXBuildFile section */ 35 | 36 | /* Begin PBXContainerItemProxy section */ 37 | 364AA31218C55EBD00EC2140 /* PBXContainerItemProxy */ = { 38 | isa = PBXContainerItemProxy; 39 | containerPortal = 364AA2E418C55EBD00EC2140 /* Project object */; 40 | proxyType = 1; 41 | remoteGlobalIDString = 364AA2EB18C55EBD00EC2140; 42 | remoteInfo = Passbooks; 43 | }; 44 | /* End PBXContainerItemProxy section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 364AA2EC18C55EBD00EC2140 /* Passbooks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passbooks.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 364AA2EF18C55EBD00EC2140 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 49 | 364AA2F118C55EBD00EC2140 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 50 | 364AA2F318C55EBD00EC2140 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 51 | 364AA2F718C55EBD00EC2140 /* Passbooks-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Passbooks-Info.plist"; sourceTree = ""; }; 52 | 364AA2F918C55EBD00EC2140 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 53 | 364AA2FB18C55EBD00EC2140 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 54 | 364AA2FD18C55EBD00EC2140 /* Passbooks-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Passbooks-Prefix.pch"; sourceTree = ""; }; 55 | 364AA2FE18C55EBD00EC2140 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 56 | 364AA2FF18C55EBD00EC2140 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 57 | 364AA30218C55EBD00EC2140 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 58 | 364AA30418C55EBD00EC2140 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 59 | 364AA30518C55EBD00EC2140 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 60 | 364AA30718C55EBD00EC2140 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 61 | 364AA30D18C55EBD00EC2140 /* PassbooksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PassbooksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 364AA30E18C55EBD00EC2140 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 63 | 364AA31618C55EBD00EC2140 /* PassbooksTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PassbooksTests-Info.plist"; sourceTree = ""; }; 64 | 364AA31818C55EBD00EC2140 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 65 | 364AA31A18C55EBD00EC2140 /* PassbooksTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PassbooksTests.m; sourceTree = ""; }; 66 | 364AA32918C5606600EC2140 /* PassCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PassCell.h; sourceTree = ""; }; 67 | 364AA32A18C5606600EC2140 /* PassCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PassCell.m; sourceTree = ""; }; 68 | BA0255EA1CB0F259002EE790 /* MTCardLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTCardLayout.h; sourceTree = ""; }; 69 | BA0255EB1CB0F259002EE790 /* MTCardLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTCardLayout.m; sourceTree = ""; }; 70 | BA0255EC1CB0F259002EE790 /* MTCardLayoutHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTCardLayoutHelper.h; sourceTree = ""; }; 71 | BA0255ED1CB0F259002EE790 /* MTCardLayoutHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTCardLayoutHelper.m; sourceTree = ""; }; 72 | BA0255EE1CB0F259002EE790 /* MTDraggableCardLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTDraggableCardLayout.h; sourceTree = ""; }; 73 | BA0255EF1CB0F259002EE790 /* MTDraggableCardLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTDraggableCardLayout.m; sourceTree = ""; }; 74 | BA0255F21CB0F259002EE790 /* UICollectionView+CardLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+CardLayout.h"; sourceTree = ""; }; 75 | BA0255F31CB0F259002EE790 /* UICollectionView+CardLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+CardLayout.m"; sourceTree = ""; }; 76 | BA0255F41CB0F259002EE790 /* UICollectionView+DraggableCardLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+DraggableCardLayout.h"; sourceTree = ""; }; 77 | BA0255F51CB0F259002EE790 /* UICollectionView+DraggableCardLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+DraggableCardLayout.m"; sourceTree = ""; }; 78 | BA17D52E196CF6A80054D9EC /* SearchViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SearchViewController.h; sourceTree = ""; }; 79 | BA17D52F196CF6A80054D9EC /* SearchViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SearchViewController.m; sourceTree = ""; }; 80 | BA2A4FE9196E71760074E49E /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 81 | BAA8824E1CBA388C00CA8035 /* MTDraggableCardLayoutHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTDraggableCardLayoutHelper.h; sourceTree = ""; }; 82 | BAA8824F1CBA388C00CA8035 /* MTDraggableCardLayoutHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTDraggableCardLayoutHelper.m; sourceTree = ""; }; 83 | C15AE6911CB254AB00640CD3 /* Launch.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Launch.storyboard; path = Passbooks/Launch.storyboard; sourceTree = ""; }; 84 | C15AE6931CB2574600640CD3 /* MTCommonTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTCommonTypes.h; sourceTree = ""; }; 85 | /* End PBXFileReference section */ 86 | 87 | /* Begin PBXFrameworksBuildPhase section */ 88 | 364AA2E918C55EBD00EC2140 /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | BA2A4FEA196E71760074E49E /* QuartzCore.framework in Frameworks */, 93 | 364AA2F218C55EBD00EC2140 /* CoreGraphics.framework in Frameworks */, 94 | 364AA2F418C55EBD00EC2140 /* UIKit.framework in Frameworks */, 95 | 364AA2F018C55EBD00EC2140 /* Foundation.framework in Frameworks */, 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | 364AA30A18C55EBD00EC2140 /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | 364AA30F18C55EBD00EC2140 /* XCTest.framework in Frameworks */, 104 | 364AA31118C55EBD00EC2140 /* UIKit.framework in Frameworks */, 105 | 364AA31018C55EBD00EC2140 /* Foundation.framework in Frameworks */, 106 | ); 107 | runOnlyForDeploymentPostprocessing = 0; 108 | }; 109 | /* End PBXFrameworksBuildPhase section */ 110 | 111 | /* Begin PBXGroup section */ 112 | 364AA2E318C55EBD00EC2140 = { 113 | isa = PBXGroup; 114 | children = ( 115 | C15AE6911CB254AB00640CD3 /* Launch.storyboard */, 116 | BA3C686619703735004A0ADF /* MTCardLayout */, 117 | 364AA2F518C55EBD00EC2140 /* Passbooks */, 118 | 364AA31418C55EBD00EC2140 /* PassbooksTests */, 119 | 364AA2EE18C55EBD00EC2140 /* Frameworks */, 120 | 364AA2ED18C55EBD00EC2140 /* Products */, 121 | ); 122 | sourceTree = ""; 123 | }; 124 | 364AA2ED18C55EBD00EC2140 /* Products */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 364AA2EC18C55EBD00EC2140 /* Passbooks.app */, 128 | 364AA30D18C55EBD00EC2140 /* PassbooksTests.xctest */, 129 | ); 130 | name = Products; 131 | sourceTree = ""; 132 | }; 133 | 364AA2EE18C55EBD00EC2140 /* Frameworks */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | BA2A4FE9196E71760074E49E /* QuartzCore.framework */, 137 | 364AA2EF18C55EBD00EC2140 /* Foundation.framework */, 138 | 364AA2F118C55EBD00EC2140 /* CoreGraphics.framework */, 139 | 364AA2F318C55EBD00EC2140 /* UIKit.framework */, 140 | 364AA30E18C55EBD00EC2140 /* XCTest.framework */, 141 | ); 142 | name = Frameworks; 143 | sourceTree = ""; 144 | }; 145 | 364AA2F518C55EBD00EC2140 /* Passbooks */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 364AA30118C55EBD00EC2140 /* Main.storyboard */, 149 | 364AA2FE18C55EBD00EC2140 /* AppDelegate.h */, 150 | 364AA2FF18C55EBD00EC2140 /* AppDelegate.m */, 151 | 364AA32918C5606600EC2140 /* PassCell.h */, 152 | 364AA32A18C5606600EC2140 /* PassCell.m */, 153 | 364AA30418C55EBD00EC2140 /* ViewController.h */, 154 | 364AA30518C55EBD00EC2140 /* ViewController.m */, 155 | BA17D52E196CF6A80054D9EC /* SearchViewController.h */, 156 | BA17D52F196CF6A80054D9EC /* SearchViewController.m */, 157 | 364AA30718C55EBD00EC2140 /* Images.xcassets */, 158 | 364AA2F618C55EBD00EC2140 /* Supporting Files */, 159 | ); 160 | path = Passbooks; 161 | sourceTree = ""; 162 | }; 163 | 364AA2F618C55EBD00EC2140 /* Supporting Files */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 364AA2F718C55EBD00EC2140 /* Passbooks-Info.plist */, 167 | 364AA2F818C55EBD00EC2140 /* InfoPlist.strings */, 168 | 364AA2FB18C55EBD00EC2140 /* main.m */, 169 | 364AA2FD18C55EBD00EC2140 /* Passbooks-Prefix.pch */, 170 | ); 171 | name = "Supporting Files"; 172 | sourceTree = ""; 173 | }; 174 | 364AA31418C55EBD00EC2140 /* PassbooksTests */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 364AA31A18C55EBD00EC2140 /* PassbooksTests.m */, 178 | 364AA31518C55EBD00EC2140 /* Supporting Files */, 179 | ); 180 | path = PassbooksTests; 181 | sourceTree = ""; 182 | }; 183 | 364AA31518C55EBD00EC2140 /* Supporting Files */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | 364AA31618C55EBD00EC2140 /* PassbooksTests-Info.plist */, 187 | 364AA31718C55EBD00EC2140 /* InfoPlist.strings */, 188 | ); 189 | name = "Supporting Files"; 190 | sourceTree = ""; 191 | }; 192 | BA3C686619703735004A0ADF /* MTCardLayout */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | C15AE6931CB2574600640CD3 /* MTCommonTypes.h */, 196 | BA0255EA1CB0F259002EE790 /* MTCardLayout.h */, 197 | BA0255EB1CB0F259002EE790 /* MTCardLayout.m */, 198 | BA0255EC1CB0F259002EE790 /* MTCardLayoutHelper.h */, 199 | BA0255ED1CB0F259002EE790 /* MTCardLayoutHelper.m */, 200 | BA0255EE1CB0F259002EE790 /* MTDraggableCardLayout.h */, 201 | BA0255EF1CB0F259002EE790 /* MTDraggableCardLayout.m */, 202 | BAA8824E1CBA388C00CA8035 /* MTDraggableCardLayoutHelper.h */, 203 | BAA8824F1CBA388C00CA8035 /* MTDraggableCardLayoutHelper.m */, 204 | BA0255F21CB0F259002EE790 /* UICollectionView+CardLayout.h */, 205 | BA0255F31CB0F259002EE790 /* UICollectionView+CardLayout.m */, 206 | BA0255F41CB0F259002EE790 /* UICollectionView+DraggableCardLayout.h */, 207 | BA0255F51CB0F259002EE790 /* UICollectionView+DraggableCardLayout.m */, 208 | ); 209 | name = MTCardLayout; 210 | path = ../../MTCardLayout; 211 | sourceTree = ""; 212 | }; 213 | /* End PBXGroup section */ 214 | 215 | /* Begin PBXNativeTarget section */ 216 | 364AA2EB18C55EBD00EC2140 /* Passbooks */ = { 217 | isa = PBXNativeTarget; 218 | buildConfigurationList = 364AA31E18C55EBD00EC2140 /* Build configuration list for PBXNativeTarget "Passbooks" */; 219 | buildPhases = ( 220 | 364AA2E818C55EBD00EC2140 /* Sources */, 221 | 364AA2E918C55EBD00EC2140 /* Frameworks */, 222 | 364AA2EA18C55EBD00EC2140 /* Resources */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | ); 228 | name = Passbooks; 229 | productName = Passbooks; 230 | productReference = 364AA2EC18C55EBD00EC2140 /* Passbooks.app */; 231 | productType = "com.apple.product-type.application"; 232 | }; 233 | 364AA30C18C55EBD00EC2140 /* PassbooksTests */ = { 234 | isa = PBXNativeTarget; 235 | buildConfigurationList = 364AA32118C55EBD00EC2140 /* Build configuration list for PBXNativeTarget "PassbooksTests" */; 236 | buildPhases = ( 237 | 364AA30918C55EBD00EC2140 /* Sources */, 238 | 364AA30A18C55EBD00EC2140 /* Frameworks */, 239 | 364AA30B18C55EBD00EC2140 /* Resources */, 240 | ); 241 | buildRules = ( 242 | ); 243 | dependencies = ( 244 | 364AA31318C55EBD00EC2140 /* PBXTargetDependency */, 245 | ); 246 | name = PassbooksTests; 247 | productName = PassbooksTests; 248 | productReference = 364AA30D18C55EBD00EC2140 /* PassbooksTests.xctest */; 249 | productType = "com.apple.product-type.bundle.unit-test"; 250 | }; 251 | /* End PBXNativeTarget section */ 252 | 253 | /* Begin PBXProject section */ 254 | 364AA2E418C55EBD00EC2140 /* Project object */ = { 255 | isa = PBXProject; 256 | attributes = { 257 | LastUpgradeCheck = 0730; 258 | ORGANIZATIONNAME = "Jose Luis Canepa"; 259 | TargetAttributes = { 260 | 364AA30C18C55EBD00EC2140 = { 261 | TestTargetID = 364AA2EB18C55EBD00EC2140; 262 | }; 263 | }; 264 | }; 265 | buildConfigurationList = 364AA2E718C55EBD00EC2140 /* Build configuration list for PBXProject "Passbooks" */; 266 | compatibilityVersion = "Xcode 3.2"; 267 | developmentRegion = English; 268 | hasScannedForEncodings = 0; 269 | knownRegions = ( 270 | en, 271 | Base, 272 | ); 273 | mainGroup = 364AA2E318C55EBD00EC2140; 274 | productRefGroup = 364AA2ED18C55EBD00EC2140 /* Products */; 275 | projectDirPath = ""; 276 | projectRoot = ""; 277 | targets = ( 278 | 364AA2EB18C55EBD00EC2140 /* Passbooks */, 279 | 364AA30C18C55EBD00EC2140 /* PassbooksTests */, 280 | ); 281 | }; 282 | /* End PBXProject section */ 283 | 284 | /* Begin PBXResourcesBuildPhase section */ 285 | 364AA2EA18C55EBD00EC2140 /* Resources */ = { 286 | isa = PBXResourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 364AA30818C55EBD00EC2140 /* Images.xcassets in Resources */, 290 | 364AA2FA18C55EBD00EC2140 /* InfoPlist.strings in Resources */, 291 | 364AA30318C55EBD00EC2140 /* Main.storyboard in Resources */, 292 | C15AE6921CB254AB00640CD3 /* Launch.storyboard in Resources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | 364AA30B18C55EBD00EC2140 /* Resources */ = { 297 | isa = PBXResourcesBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | 364AA31918C55EBD00EC2140 /* InfoPlist.strings in Resources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | /* End PBXResourcesBuildPhase section */ 305 | 306 | /* Begin PBXSourcesBuildPhase section */ 307 | 364AA2E818C55EBD00EC2140 /* Sources */ = { 308 | isa = PBXSourcesBuildPhase; 309 | buildActionMask = 2147483647; 310 | files = ( 311 | BA0255F61CB0F259002EE790 /* MTCardLayout.m in Sources */, 312 | BAA882501CBA388C00CA8035 /* MTDraggableCardLayoutHelper.m in Sources */, 313 | BA0255FB1CB0F259002EE790 /* UICollectionView+DraggableCardLayout.m in Sources */, 314 | 364AA30618C55EBD00EC2140 /* ViewController.m in Sources */, 315 | 364AA30018C55EBD00EC2140 /* AppDelegate.m in Sources */, 316 | BA17D530196CF6A80054D9EC /* SearchViewController.m in Sources */, 317 | BA0255F81CB0F259002EE790 /* MTDraggableCardLayout.m in Sources */, 318 | 364AA2FC18C55EBD00EC2140 /* main.m in Sources */, 319 | BA0255FA1CB0F259002EE790 /* UICollectionView+CardLayout.m in Sources */, 320 | 364AA32B18C5606600EC2140 /* PassCell.m in Sources */, 321 | BA0255F71CB0F259002EE790 /* MTCardLayoutHelper.m in Sources */, 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | 364AA30918C55EBD00EC2140 /* Sources */ = { 326 | isa = PBXSourcesBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | 364AA31B18C55EBD00EC2140 /* PassbooksTests.m in Sources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | /* End PBXSourcesBuildPhase section */ 334 | 335 | /* Begin PBXTargetDependency section */ 336 | 364AA31318C55EBD00EC2140 /* PBXTargetDependency */ = { 337 | isa = PBXTargetDependency; 338 | target = 364AA2EB18C55EBD00EC2140 /* Passbooks */; 339 | targetProxy = 364AA31218C55EBD00EC2140 /* PBXContainerItemProxy */; 340 | }; 341 | /* End PBXTargetDependency section */ 342 | 343 | /* Begin PBXVariantGroup section */ 344 | 364AA2F818C55EBD00EC2140 /* InfoPlist.strings */ = { 345 | isa = PBXVariantGroup; 346 | children = ( 347 | 364AA2F918C55EBD00EC2140 /* en */, 348 | ); 349 | name = InfoPlist.strings; 350 | sourceTree = ""; 351 | }; 352 | 364AA30118C55EBD00EC2140 /* Main.storyboard */ = { 353 | isa = PBXVariantGroup; 354 | children = ( 355 | 364AA30218C55EBD00EC2140 /* Base */, 356 | ); 357 | name = Main.storyboard; 358 | sourceTree = ""; 359 | }; 360 | 364AA31718C55EBD00EC2140 /* InfoPlist.strings */ = { 361 | isa = PBXVariantGroup; 362 | children = ( 363 | 364AA31818C55EBD00EC2140 /* en */, 364 | ); 365 | name = InfoPlist.strings; 366 | sourceTree = ""; 367 | }; 368 | /* End PBXVariantGroup section */ 369 | 370 | /* Begin XCBuildConfiguration section */ 371 | 364AA31C18C55EBD00EC2140 /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ALWAYS_SEARCH_USER_PATHS = NO; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 382 | CLANG_WARN_EMPTY_BODY = YES; 383 | CLANG_WARN_ENUM_CONVERSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 386 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 387 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 388 | COPY_PHASE_STRIP = NO; 389 | ENABLE_TESTABILITY = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_DYNAMIC_NO_PIC = NO; 392 | GCC_OPTIMIZATION_LEVEL = 0; 393 | GCC_PREPROCESSOR_DEFINITIONS = ( 394 | "DEBUG=1", 395 | "$(inherited)", 396 | ); 397 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 398 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 399 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 400 | GCC_WARN_UNDECLARED_SELECTOR = YES; 401 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 402 | GCC_WARN_UNUSED_FUNCTION = YES; 403 | GCC_WARN_UNUSED_VARIABLE = YES; 404 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 405 | ONLY_ACTIVE_ARCH = YES; 406 | SDKROOT = iphoneos; 407 | }; 408 | name = Debug; 409 | }; 410 | 364AA31D18C55EBD00EC2140 /* Release */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ALWAYS_SEARCH_USER_PATHS = NO; 414 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 415 | CLANG_CXX_LIBRARY = "libc++"; 416 | CLANG_ENABLE_MODULES = YES; 417 | CLANG_ENABLE_OBJC_ARC = YES; 418 | CLANG_WARN_BOOL_CONVERSION = YES; 419 | CLANG_WARN_CONSTANT_CONVERSION = YES; 420 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 421 | CLANG_WARN_EMPTY_BODY = YES; 422 | CLANG_WARN_ENUM_CONVERSION = YES; 423 | CLANG_WARN_INT_CONVERSION = YES; 424 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 427 | COPY_PHASE_STRIP = YES; 428 | ENABLE_NS_ASSERTIONS = NO; 429 | GCC_C_LANGUAGE_STANDARD = gnu99; 430 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 431 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 432 | GCC_WARN_UNDECLARED_SELECTOR = YES; 433 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 434 | GCC_WARN_UNUSED_FUNCTION = YES; 435 | GCC_WARN_UNUSED_VARIABLE = YES; 436 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 437 | SDKROOT = iphoneos; 438 | VALIDATE_PRODUCT = YES; 439 | }; 440 | name = Release; 441 | }; 442 | 364AA31F18C55EBD00EC2140 /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 446 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 447 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 448 | GCC_PREFIX_HEADER = "Passbooks/Passbooks-Prefix.pch"; 449 | INFOPLIST_FILE = "Passbooks/Passbooks-Info.plist"; 450 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 451 | PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME:rfc1034identifier}"; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | WRAPPER_EXTENSION = app; 454 | }; 455 | name = Debug; 456 | }; 457 | 364AA32018C55EBD00EC2140 /* Release */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 461 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 462 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 463 | GCC_PREFIX_HEADER = "Passbooks/Passbooks-Prefix.pch"; 464 | INFOPLIST_FILE = "Passbooks/Passbooks-Info.plist"; 465 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 466 | PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME:rfc1034identifier}"; 467 | PRODUCT_NAME = "$(TARGET_NAME)"; 468 | WRAPPER_EXTENSION = app; 469 | }; 470 | name = Release; 471 | }; 472 | 364AA32218C55EBD00EC2140 /* Debug */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Passbooks.app/Passbooks"; 476 | FRAMEWORK_SEARCH_PATHS = ( 477 | "$(SDKROOT)/Developer/Library/Frameworks", 478 | "$(inherited)", 479 | "$(DEVELOPER_FRAMEWORKS_DIR)", 480 | ); 481 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 482 | GCC_PREFIX_HEADER = "Passbooks/Passbooks-Prefix.pch"; 483 | GCC_PREPROCESSOR_DEFINITIONS = ( 484 | "DEBUG=1", 485 | "$(inherited)", 486 | ); 487 | INFOPLIST_FILE = "PassbooksTests/PassbooksTests-Info.plist"; 488 | PRODUCT_BUNDLE_IDENTIFIER = "-R52ZCQDKB.im.can.${PRODUCT_NAME:rfc1034identifier}"; 489 | PRODUCT_NAME = "$(TARGET_NAME)"; 490 | TEST_HOST = "$(BUNDLE_LOADER)"; 491 | WRAPPER_EXTENSION = xctest; 492 | }; 493 | name = Debug; 494 | }; 495 | 364AA32318C55EBD00EC2140 /* Release */ = { 496 | isa = XCBuildConfiguration; 497 | buildSettings = { 498 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Passbooks.app/Passbooks"; 499 | FRAMEWORK_SEARCH_PATHS = ( 500 | "$(SDKROOT)/Developer/Library/Frameworks", 501 | "$(inherited)", 502 | "$(DEVELOPER_FRAMEWORKS_DIR)", 503 | ); 504 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 505 | GCC_PREFIX_HEADER = "Passbooks/Passbooks-Prefix.pch"; 506 | INFOPLIST_FILE = "PassbooksTests/PassbooksTests-Info.plist"; 507 | PRODUCT_BUNDLE_IDENTIFIER = "-R52ZCQDKB.im.can.${PRODUCT_NAME:rfc1034identifier}"; 508 | PRODUCT_NAME = "$(TARGET_NAME)"; 509 | TEST_HOST = "$(BUNDLE_LOADER)"; 510 | WRAPPER_EXTENSION = xctest; 511 | }; 512 | name = Release; 513 | }; 514 | /* End XCBuildConfiguration section */ 515 | 516 | /* Begin XCConfigurationList section */ 517 | 364AA2E718C55EBD00EC2140 /* Build configuration list for PBXProject "Passbooks" */ = { 518 | isa = XCConfigurationList; 519 | buildConfigurations = ( 520 | 364AA31C18C55EBD00EC2140 /* Debug */, 521 | 364AA31D18C55EBD00EC2140 /* Release */, 522 | ); 523 | defaultConfigurationIsVisible = 0; 524 | defaultConfigurationName = Release; 525 | }; 526 | 364AA31E18C55EBD00EC2140 /* Build configuration list for PBXNativeTarget "Passbooks" */ = { 527 | isa = XCConfigurationList; 528 | buildConfigurations = ( 529 | 364AA31F18C55EBD00EC2140 /* Debug */, 530 | 364AA32018C55EBD00EC2140 /* Release */, 531 | ); 532 | defaultConfigurationIsVisible = 0; 533 | defaultConfigurationName = Release; 534 | }; 535 | 364AA32118C55EBD00EC2140 /* Build configuration list for PBXNativeTarget "PassbooksTests" */ = { 536 | isa = XCConfigurationList; 537 | buildConfigurations = ( 538 | 364AA32218C55EBD00EC2140 /* Debug */, 539 | 364AA32318C55EBD00EC2140 /* Release */, 540 | ); 541 | defaultConfigurationIsVisible = 0; 542 | defaultConfigurationName = Release; 543 | }; 544 | /* End XCConfigurationList section */ 545 | }; 546 | rootObject = 364AA2E418C55EBD00EC2140 /* Project object */; 547 | } 548 | --------------------------------------------------------------------------------