├── .gitignore ├── LICENCE ├── README.md ├── SECollectionViewFlowLayout.podspec ├── SECollectionViewFlowLayout.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── ChrisW.xcuserdatad │ └── xcschemes │ ├── SECollectionViewFlowLayout.xcscheme │ └── xcschememanagement.plist ├── SECollectionViewFlowLayout ├── Base.lproj │ └── Main.storyboard ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── PodFiles │ ├── QBAssetsCollectionCheckmarkView.h │ ├── QBAssetsCollectionCheckmarkView.m │ ├── QBAssetsCollectionFooterView.h │ ├── QBAssetsCollectionFooterView.m │ ├── QBAssetsCollectionOverlayView.h │ ├── QBAssetsCollectionOverlayView.m │ ├── QBAssetsCollectionViewCell.h │ ├── QBAssetsCollectionViewCell.m │ ├── QBAssetsCollectionViewController.h │ ├── QBAssetsCollectionViewController.m │ ├── QBImagePickerController.h │ ├── QBImagePickerController.m │ ├── QBImagePickerGroupCell.h │ ├── QBImagePickerGroupCell.m │ ├── QBImagePickerThumbnailView.h │ ├── QBImagePickerThumbnailView.m │ ├── SECollectionViewFlowLayout.h │ └── SECollectionViewFlowLayout.m ├── SEAppDelegate.h ├── SEAppDelegate.m ├── SECollectionViewFlowLayout-Info.plist ├── SECollectionViewFlowLayout-Prefix.pch ├── SEViewController.h ├── SEViewController.m ├── en.lproj │ └── InfoPlist.strings └── main.m └── SEQBImagePickerController.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/* 3 | *.pbxuser 4 | *.xcworkspace 5 | xcuserdata 6 | profile 7 | *.moved-aside 8 | xcuserdata/ 9 | project.xcworkspace/ 10 | xcshareddata/ 11 | *.swp 12 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Christopher Wendel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SECollectionViewFlowLayout 2 | ========================== 3 | 4 | A flow layout for UICollectionView that implements swiping-to-select gestures. 5 | 6 | ##Functionality 7 | ###Panning to select 8 | Just touch down and pan and select items in your UICollectionView, much easier than tapping each item 9 |

10 | 11 | ###Auto-select rows 12 | If you choose you can enable auto-selecting rows, where if you pan to select an entire row you can continue panning down to select whole rows at a time. 13 | 14 |

15 | 16 | ###Pan to deselect 17 | Along with panning to select collection view cells, you can choose to enable panning to deselect, where if you start panning from a selected cell, the panning will deselect cells. 18 | 19 |

20 | 21 | ###Auto select cells between touches 22 | If you choose you can enable auto selection of cells between a first and second touch. Where all cells between the two touches will be selected. 23 | 24 |

25 | 26 | 27 | ##SEQBImagePickerController 28 | The example use case (as seen in the gifs) is using SECollectionViewFlowLayout in combination with [QBImagePickerController](https://github.com/questbeat/QBImagePickerController) to select multiple photos from a UIImagePickerController clone. You can use this image picker in your project by adding to your podfile: 29 |
pod 'SEQBImagePickerController' 
30 | 31 | ##SECollectionViewFlowLayout 32 | You can also use SECollectionViewFlowLayout in your project and use it with your own UICollectionView. 33 |
pod 'SECollectionViewFlowLayout' 
34 | 35 | ##Usage 36 | 37 | When initializing your `UICollectionViewController` using `initWithCollectionViewLayout:`, allocate a new `SECollectionViewFlowLayout` 38 | 39 | ```objc 40 | UICollectionViewController *collectionViewController = 41 | [[UICollectionViewController alloc] initWithCollectionViewLayout: 42 | [SECollectionViewFlowLayout layoutWithAutoSelectRows:YES panToDeselect:YES autoSelectCellsBetweenTouches:YES]]; 43 | ``` 44 | 45 | ##Contributing 46 | Use [Github issues](https://github.com/cewendel/SECollectionViewFlowLayout/issues) to track bugs and feature requests 47 | 48 | ##Contact 49 | 50 | Chris Wendel 51 | - http://twitter.com/CEWendel 52 | - [chriwend@umich.edu](mailto:chriwend@umich.edu) 53 | 54 | ##Licence 55 | 56 | MIT 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SECollectionViewFlowLayout' 3 | s.version = '0.0.1' 4 | s.author = { 'Chris Wendel' => 'chriwend@umich.edu' } 5 | s.homepage = 'https://github.com/CEWendel/SECollectionViewFlowLayout' 6 | s.summary = 'A flow layout for UICollectionView that implements swiping-to-select gestures' 7 | s.license = 'MIT' 8 | s.source = { :git => 'https://github.com/CEWendel/SECollectionViewFlowLayout.git', :tag => '0.0.1' } 9 | s.source_files = 'SECollectionViewFlowLayout/PodFiles/SECollectionViewFlowLayout.{h,m}' 10 | s.platform = :ios 11 | s.ios.deployment_target = '6.0' 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AF2471FB18B30BAD00614655 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF2471FA18B30BAD00614655 /* Foundation.framework */; }; 11 | AF2471FD18B30BAD00614655 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF2471FC18B30BAD00614655 /* CoreGraphics.framework */; }; 12 | AF2471FF18B30BAD00614655 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF2471FE18B30BAD00614655 /* UIKit.framework */; }; 13 | AF24720518B30BAD00614655 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AF24720318B30BAD00614655 /* InfoPlist.strings */; }; 14 | AF24720718B30BAD00614655 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24720618B30BAD00614655 /* main.m */; }; 15 | AF24720B18B30BAD00614655 /* SEAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24720A18B30BAD00614655 /* SEAppDelegate.m */; }; 16 | AF24720E18B30BAD00614655 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AF24720C18B30BAD00614655 /* Main.storyboard */; }; 17 | AF24721118B30BAD00614655 /* SEViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24721018B30BAD00614655 /* SEViewController.m */; }; 18 | AF24721318B30BAD00614655 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AF24721218B30BAD00614655 /* Images.xcassets */; }; 19 | AF24721A18B30BAD00614655 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF24721918B30BAD00614655 /* XCTest.framework */; }; 20 | AF24721B18B30BAD00614655 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF2471FA18B30BAD00614655 /* Foundation.framework */; }; 21 | AF24721C18B30BAD00614655 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF2471FE18B30BAD00614655 /* UIKit.framework */; }; 22 | AF24722418B30BAD00614655 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AF24722218B30BAD00614655 /* InfoPlist.strings */; }; 23 | AF24722618B30BAD00614655 /* SECollectionViewFlowLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24722518B30BAD00614655 /* SECollectionViewFlowLayoutTests.m */; }; 24 | AF24724B18B30D1A00614655 /* SECollectionViewFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24724A18B30D1A00614655 /* SECollectionViewFlowLayout.m */; }; 25 | AF24725D18B32D8D00614655 /* QBImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24724D18B32D8D00614655 /* QBImagePickerController.m */; }; 26 | AF24725E18B32D8D00614655 /* QBAssetsCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24724E18B32D8D00614655 /* QBAssetsCollectionViewController.m */; }; 27 | AF24725F18B32D8D00614655 /* QBAssetsCollectionOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24725018B32D8D00614655 /* QBAssetsCollectionOverlayView.m */; }; 28 | AF24726018B32D8D00614655 /* QBAssetsCollectionCheckmarkView.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24725118B32D8D00614655 /* QBAssetsCollectionCheckmarkView.m */; }; 29 | AF24726118B32D8D00614655 /* QBAssetsCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24725218B32D8D00614655 /* QBAssetsCollectionViewCell.m */; }; 30 | AF24726218B32D8D00614655 /* QBAssetsCollectionFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24725518B32D8D00614655 /* QBAssetsCollectionFooterView.m */; }; 31 | AF24726318B32D8D00614655 /* QBImagePickerGroupCell.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24725A18B32D8D00614655 /* QBImagePickerGroupCell.m */; }; 32 | AF24726418B32D8D00614655 /* QBImagePickerThumbnailView.m in Sources */ = {isa = PBXBuildFile; fileRef = AF24725C18B32D8D00614655 /* QBImagePickerThumbnailView.m */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | AF24721D18B30BAD00614655 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = AF2471EF18B30BAD00614655 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = AF2471F618B30BAD00614655; 41 | remoteInfo = SECollectionViewFlowLayout; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | AF2471F718B30BAD00614655 /* SECollectionViewFlowLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SECollectionViewFlowLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | AF2471FA18B30BAD00614655 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 48 | AF2471FC18B30BAD00614655 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 49 | AF2471FE18B30BAD00614655 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 50 | AF24720218B30BAD00614655 /* SECollectionViewFlowLayout-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SECollectionViewFlowLayout-Info.plist"; sourceTree = ""; }; 51 | AF24720418B30BAD00614655 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 52 | AF24720618B30BAD00614655 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 53 | AF24720818B30BAD00614655 /* SECollectionViewFlowLayout-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SECollectionViewFlowLayout-Prefix.pch"; sourceTree = ""; }; 54 | AF24720918B30BAD00614655 /* SEAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEAppDelegate.h; sourceTree = ""; }; 55 | AF24720A18B30BAD00614655 /* SEAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEAppDelegate.m; sourceTree = ""; }; 56 | AF24720D18B30BAD00614655 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | AF24720F18B30BAD00614655 /* SEViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEViewController.h; sourceTree = ""; }; 58 | AF24721018B30BAD00614655 /* SEViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEViewController.m; sourceTree = ""; }; 59 | AF24721218B30BAD00614655 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 60 | AF24721818B30BAD00614655 /* SECollectionViewFlowLayoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SECollectionViewFlowLayoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | AF24721918B30BAD00614655 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 62 | AF24722118B30BAD00614655 /* SECollectionViewFlowLayoutTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SECollectionViewFlowLayoutTests-Info.plist"; sourceTree = ""; }; 63 | AF24722318B30BAD00614655 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 64 | AF24722518B30BAD00614655 /* SECollectionViewFlowLayoutTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SECollectionViewFlowLayoutTests.m; sourceTree = ""; }; 65 | AF24724918B30D1A00614655 /* SECollectionViewFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SECollectionViewFlowLayout.h; path = PodFiles/SECollectionViewFlowLayout.h; sourceTree = ""; }; 66 | AF24724A18B30D1A00614655 /* SECollectionViewFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SECollectionViewFlowLayout.m; path = PodFiles/SECollectionViewFlowLayout.m; sourceTree = ""; }; 67 | AF24724D18B32D8D00614655 /* QBImagePickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QBImagePickerController.m; path = PodFiles/QBImagePickerController.m; sourceTree = ""; }; 68 | AF24724E18B32D8D00614655 /* QBAssetsCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QBAssetsCollectionViewController.m; path = PodFiles/QBAssetsCollectionViewController.m; sourceTree = ""; }; 69 | AF24724F18B32D8D00614655 /* QBAssetsCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QBAssetsCollectionViewController.h; path = PodFiles/QBAssetsCollectionViewController.h; sourceTree = ""; }; 70 | AF24725018B32D8D00614655 /* QBAssetsCollectionOverlayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QBAssetsCollectionOverlayView.m; path = PodFiles/QBAssetsCollectionOverlayView.m; sourceTree = ""; }; 71 | AF24725118B32D8D00614655 /* QBAssetsCollectionCheckmarkView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QBAssetsCollectionCheckmarkView.m; path = PodFiles/QBAssetsCollectionCheckmarkView.m; sourceTree = ""; }; 72 | AF24725218B32D8D00614655 /* QBAssetsCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QBAssetsCollectionViewCell.m; path = PodFiles/QBAssetsCollectionViewCell.m; sourceTree = ""; }; 73 | AF24725318B32D8D00614655 /* QBAssetsCollectionCheckmarkView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QBAssetsCollectionCheckmarkView.h; path = PodFiles/QBAssetsCollectionCheckmarkView.h; sourceTree = ""; }; 74 | AF24725418B32D8D00614655 /* QBAssetsCollectionFooterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QBAssetsCollectionFooterView.h; path = PodFiles/QBAssetsCollectionFooterView.h; sourceTree = ""; }; 75 | AF24725518B32D8D00614655 /* QBAssetsCollectionFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QBAssetsCollectionFooterView.m; path = PodFiles/QBAssetsCollectionFooterView.m; sourceTree = ""; }; 76 | AF24725618B32D8D00614655 /* QBAssetsCollectionOverlayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QBAssetsCollectionOverlayView.h; path = PodFiles/QBAssetsCollectionOverlayView.h; sourceTree = ""; }; 77 | AF24725718B32D8D00614655 /* QBAssetsCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QBAssetsCollectionViewCell.h; path = PodFiles/QBAssetsCollectionViewCell.h; sourceTree = ""; }; 78 | AF24725818B32D8D00614655 /* QBImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QBImagePickerController.h; path = PodFiles/QBImagePickerController.h; sourceTree = ""; }; 79 | AF24725918B32D8D00614655 /* QBImagePickerGroupCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QBImagePickerGroupCell.h; path = PodFiles/QBImagePickerGroupCell.h; sourceTree = ""; }; 80 | AF24725A18B32D8D00614655 /* QBImagePickerGroupCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QBImagePickerGroupCell.m; path = PodFiles/QBImagePickerGroupCell.m; sourceTree = ""; }; 81 | AF24725B18B32D8D00614655 /* QBImagePickerThumbnailView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QBImagePickerThumbnailView.h; path = PodFiles/QBImagePickerThumbnailView.h; sourceTree = ""; }; 82 | AF24725C18B32D8D00614655 /* QBImagePickerThumbnailView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = QBImagePickerThumbnailView.m; path = PodFiles/QBImagePickerThumbnailView.m; sourceTree = ""; }; 83 | /* End PBXFileReference section */ 84 | 85 | /* Begin PBXFrameworksBuildPhase section */ 86 | AF2471F418B30BAD00614655 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | AF2471FD18B30BAD00614655 /* CoreGraphics.framework in Frameworks */, 91 | AF2471FF18B30BAD00614655 /* UIKit.framework in Frameworks */, 92 | AF2471FB18B30BAD00614655 /* Foundation.framework in Frameworks */, 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | AF24721518B30BAD00614655 /* Frameworks */ = { 97 | isa = PBXFrameworksBuildPhase; 98 | buildActionMask = 2147483647; 99 | files = ( 100 | AF24721A18B30BAD00614655 /* XCTest.framework in Frameworks */, 101 | AF24721C18B30BAD00614655 /* UIKit.framework in Frameworks */, 102 | AF24721B18B30BAD00614655 /* Foundation.framework in Frameworks */, 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | /* End PBXFrameworksBuildPhase section */ 107 | 108 | /* Begin PBXGroup section */ 109 | AF2471EE18B30BAD00614655 = { 110 | isa = PBXGroup; 111 | children = ( 112 | AF24720018B30BAD00614655 /* SECollectionViewFlowLayout */, 113 | AF24721F18B30BAD00614655 /* SECollectionViewFlowLayoutTests */, 114 | AF2471F918B30BAD00614655 /* Frameworks */, 115 | AF2471F818B30BAD00614655 /* Products */, 116 | ); 117 | sourceTree = ""; 118 | }; 119 | AF2471F818B30BAD00614655 /* Products */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | AF2471F718B30BAD00614655 /* SECollectionViewFlowLayout.app */, 123 | AF24721818B30BAD00614655 /* SECollectionViewFlowLayoutTests.xctest */, 124 | ); 125 | name = Products; 126 | sourceTree = ""; 127 | }; 128 | AF2471F918B30BAD00614655 /* Frameworks */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | AF2471FA18B30BAD00614655 /* Foundation.framework */, 132 | AF2471FC18B30BAD00614655 /* CoreGraphics.framework */, 133 | AF2471FE18B30BAD00614655 /* UIKit.framework */, 134 | AF24721918B30BAD00614655 /* XCTest.framework */, 135 | ); 136 | name = Frameworks; 137 | sourceTree = ""; 138 | }; 139 | AF24720018B30BAD00614655 /* SECollectionViewFlowLayout */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | AF24724C18B32C8400614655 /* PodFiles */, 143 | AF24720918B30BAD00614655 /* SEAppDelegate.h */, 144 | AF24720A18B30BAD00614655 /* SEAppDelegate.m */, 145 | AF24720C18B30BAD00614655 /* Main.storyboard */, 146 | AF24720F18B30BAD00614655 /* SEViewController.h */, 147 | AF24721018B30BAD00614655 /* SEViewController.m */, 148 | AF24721218B30BAD00614655 /* Images.xcassets */, 149 | AF24720118B30BAD00614655 /* Supporting Files */, 150 | ); 151 | path = SECollectionViewFlowLayout; 152 | sourceTree = ""; 153 | }; 154 | AF24720118B30BAD00614655 /* Supporting Files */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | AF24720218B30BAD00614655 /* SECollectionViewFlowLayout-Info.plist */, 158 | AF24720318B30BAD00614655 /* InfoPlist.strings */, 159 | AF24720618B30BAD00614655 /* main.m */, 160 | AF24720818B30BAD00614655 /* SECollectionViewFlowLayout-Prefix.pch */, 161 | ); 162 | name = "Supporting Files"; 163 | sourceTree = ""; 164 | }; 165 | AF24721F18B30BAD00614655 /* SECollectionViewFlowLayoutTests */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | AF24722518B30BAD00614655 /* SECollectionViewFlowLayoutTests.m */, 169 | AF24722018B30BAD00614655 /* Supporting Files */, 170 | ); 171 | path = SECollectionViewFlowLayoutTests; 172 | sourceTree = ""; 173 | }; 174 | AF24722018B30BAD00614655 /* Supporting Files */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | AF24722118B30BAD00614655 /* SECollectionViewFlowLayoutTests-Info.plist */, 178 | AF24722218B30BAD00614655 /* InfoPlist.strings */, 179 | ); 180 | name = "Supporting Files"; 181 | sourceTree = ""; 182 | }; 183 | AF24722F18B30C2300614655 /* QBImagePickerController */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | AF24725818B32D8D00614655 /* QBImagePickerController.h */, 187 | AF24724D18B32D8D00614655 /* QBImagePickerController.m */, 188 | AF24724F18B32D8D00614655 /* QBAssetsCollectionViewController.h */, 189 | AF24724E18B32D8D00614655 /* QBAssetsCollectionViewController.m */, 190 | AF24725618B32D8D00614655 /* QBAssetsCollectionOverlayView.h */, 191 | AF24725018B32D8D00614655 /* QBAssetsCollectionOverlayView.m */, 192 | AF24725318B32D8D00614655 /* QBAssetsCollectionCheckmarkView.h */, 193 | AF24725118B32D8D00614655 /* QBAssetsCollectionCheckmarkView.m */, 194 | AF24725718B32D8D00614655 /* QBAssetsCollectionViewCell.h */, 195 | AF24725218B32D8D00614655 /* QBAssetsCollectionViewCell.m */, 196 | AF24725418B32D8D00614655 /* QBAssetsCollectionFooterView.h */, 197 | AF24725518B32D8D00614655 /* QBAssetsCollectionFooterView.m */, 198 | AF24725918B32D8D00614655 /* QBImagePickerGroupCell.h */, 199 | AF24725A18B32D8D00614655 /* QBImagePickerGroupCell.m */, 200 | AF24725B18B32D8D00614655 /* QBImagePickerThumbnailView.h */, 201 | AF24725C18B32D8D00614655 /* QBImagePickerThumbnailView.m */, 202 | ); 203 | name = QBImagePickerController; 204 | sourceTree = ""; 205 | }; 206 | AF24724C18B32C8400614655 /* PodFiles */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | AF24724918B30D1A00614655 /* SECollectionViewFlowLayout.h */, 210 | AF24724A18B30D1A00614655 /* SECollectionViewFlowLayout.m */, 211 | AF24722F18B30C2300614655 /* QBImagePickerController */, 212 | ); 213 | name = PodFiles; 214 | sourceTree = ""; 215 | }; 216 | /* End PBXGroup section */ 217 | 218 | /* Begin PBXNativeTarget section */ 219 | AF2471F618B30BAD00614655 /* SECollectionViewFlowLayout */ = { 220 | isa = PBXNativeTarget; 221 | buildConfigurationList = AF24722918B30BAD00614655 /* Build configuration list for PBXNativeTarget "SECollectionViewFlowLayout" */; 222 | buildPhases = ( 223 | AF2471F318B30BAD00614655 /* Sources */, 224 | AF2471F418B30BAD00614655 /* Frameworks */, 225 | AF2471F518B30BAD00614655 /* Resources */, 226 | ); 227 | buildRules = ( 228 | ); 229 | dependencies = ( 230 | ); 231 | name = SECollectionViewFlowLayout; 232 | productName = SECollectionViewFlowLayout; 233 | productReference = AF2471F718B30BAD00614655 /* SECollectionViewFlowLayout.app */; 234 | productType = "com.apple.product-type.application"; 235 | }; 236 | AF24721718B30BAD00614655 /* SECollectionViewFlowLayoutTests */ = { 237 | isa = PBXNativeTarget; 238 | buildConfigurationList = AF24722C18B30BAD00614655 /* Build configuration list for PBXNativeTarget "SECollectionViewFlowLayoutTests" */; 239 | buildPhases = ( 240 | AF24721418B30BAD00614655 /* Sources */, 241 | AF24721518B30BAD00614655 /* Frameworks */, 242 | AF24721618B30BAD00614655 /* Resources */, 243 | ); 244 | buildRules = ( 245 | ); 246 | dependencies = ( 247 | AF24721E18B30BAD00614655 /* PBXTargetDependency */, 248 | ); 249 | name = SECollectionViewFlowLayoutTests; 250 | productName = SECollectionViewFlowLayoutTests; 251 | productReference = AF24721818B30BAD00614655 /* SECollectionViewFlowLayoutTests.xctest */; 252 | productType = "com.apple.product-type.bundle.unit-test"; 253 | }; 254 | /* End PBXNativeTarget section */ 255 | 256 | /* Begin PBXProject section */ 257 | AF2471EF18B30BAD00614655 /* Project object */ = { 258 | isa = PBXProject; 259 | attributes = { 260 | CLASSPREFIX = SE; 261 | LastUpgradeCheck = 0500; 262 | ORGANIZATIONNAME = CEWendel; 263 | TargetAttributes = { 264 | AF24721718B30BAD00614655 = { 265 | TestTargetID = AF2471F618B30BAD00614655; 266 | }; 267 | }; 268 | }; 269 | buildConfigurationList = AF2471F218B30BAD00614655 /* Build configuration list for PBXProject "SECollectionViewFlowLayout" */; 270 | compatibilityVersion = "Xcode 3.2"; 271 | developmentRegion = English; 272 | hasScannedForEncodings = 0; 273 | knownRegions = ( 274 | en, 275 | Base, 276 | ); 277 | mainGroup = AF2471EE18B30BAD00614655; 278 | productRefGroup = AF2471F818B30BAD00614655 /* Products */; 279 | projectDirPath = ""; 280 | projectRoot = ""; 281 | targets = ( 282 | AF2471F618B30BAD00614655 /* SECollectionViewFlowLayout */, 283 | AF24721718B30BAD00614655 /* SECollectionViewFlowLayoutTests */, 284 | ); 285 | }; 286 | /* End PBXProject section */ 287 | 288 | /* Begin PBXResourcesBuildPhase section */ 289 | AF2471F518B30BAD00614655 /* Resources */ = { 290 | isa = PBXResourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | AF24721318B30BAD00614655 /* Images.xcassets in Resources */, 294 | AF24720518B30BAD00614655 /* InfoPlist.strings in Resources */, 295 | AF24720E18B30BAD00614655 /* Main.storyboard in Resources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | AF24721618B30BAD00614655 /* Resources */ = { 300 | isa = PBXResourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | AF24722418B30BAD00614655 /* InfoPlist.strings in Resources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXResourcesBuildPhase section */ 308 | 309 | /* Begin PBXSourcesBuildPhase section */ 310 | AF2471F318B30BAD00614655 /* Sources */ = { 311 | isa = PBXSourcesBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | AF24720B18B30BAD00614655 /* SEAppDelegate.m in Sources */, 315 | AF24726118B32D8D00614655 /* QBAssetsCollectionViewCell.m in Sources */, 316 | AF24721118B30BAD00614655 /* SEViewController.m in Sources */, 317 | AF24726218B32D8D00614655 /* QBAssetsCollectionFooterView.m in Sources */, 318 | AF24726018B32D8D00614655 /* QBAssetsCollectionCheckmarkView.m in Sources */, 319 | AF24725E18B32D8D00614655 /* QBAssetsCollectionViewController.m in Sources */, 320 | AF24725D18B32D8D00614655 /* QBImagePickerController.m in Sources */, 321 | AF24720718B30BAD00614655 /* main.m in Sources */, 322 | AF24724B18B30D1A00614655 /* SECollectionViewFlowLayout.m in Sources */, 323 | AF24725F18B32D8D00614655 /* QBAssetsCollectionOverlayView.m in Sources */, 324 | AF24726418B32D8D00614655 /* QBImagePickerThumbnailView.m in Sources */, 325 | AF24726318B32D8D00614655 /* QBImagePickerGroupCell.m in Sources */, 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | }; 329 | AF24721418B30BAD00614655 /* Sources */ = { 330 | isa = PBXSourcesBuildPhase; 331 | buildActionMask = 2147483647; 332 | files = ( 333 | AF24722618B30BAD00614655 /* SECollectionViewFlowLayoutTests.m in Sources */, 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | /* End PBXSourcesBuildPhase section */ 338 | 339 | /* Begin PBXTargetDependency section */ 340 | AF24721E18B30BAD00614655 /* PBXTargetDependency */ = { 341 | isa = PBXTargetDependency; 342 | target = AF2471F618B30BAD00614655 /* SECollectionViewFlowLayout */; 343 | targetProxy = AF24721D18B30BAD00614655 /* PBXContainerItemProxy */; 344 | }; 345 | /* End PBXTargetDependency section */ 346 | 347 | /* Begin PBXVariantGroup section */ 348 | AF24720318B30BAD00614655 /* InfoPlist.strings */ = { 349 | isa = PBXVariantGroup; 350 | children = ( 351 | AF24720418B30BAD00614655 /* en */, 352 | ); 353 | name = InfoPlist.strings; 354 | sourceTree = ""; 355 | }; 356 | AF24720C18B30BAD00614655 /* Main.storyboard */ = { 357 | isa = PBXVariantGroup; 358 | children = ( 359 | AF24720D18B30BAD00614655 /* Base */, 360 | ); 361 | name = Main.storyboard; 362 | sourceTree = ""; 363 | }; 364 | AF24722218B30BAD00614655 /* InfoPlist.strings */ = { 365 | isa = PBXVariantGroup; 366 | children = ( 367 | AF24722318B30BAD00614655 /* en */, 368 | ); 369 | name = InfoPlist.strings; 370 | sourceTree = ""; 371 | }; 372 | /* End PBXVariantGroup section */ 373 | 374 | /* Begin XCBuildConfiguration section */ 375 | AF24722718B30BAD00614655 /* Debug */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | ALWAYS_SEARCH_USER_PATHS = NO; 379 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 380 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 381 | CLANG_CXX_LIBRARY = "libc++"; 382 | CLANG_ENABLE_MODULES = YES; 383 | CLANG_ENABLE_OBJC_ARC = YES; 384 | CLANG_WARN_BOOL_CONVERSION = YES; 385 | CLANG_WARN_CONSTANT_CONVERSION = YES; 386 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 387 | CLANG_WARN_EMPTY_BODY = YES; 388 | CLANG_WARN_ENUM_CONVERSION = YES; 389 | CLANG_WARN_INT_CONVERSION = YES; 390 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 391 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 392 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 393 | COPY_PHASE_STRIP = NO; 394 | GCC_C_LANGUAGE_STANDARD = gnu99; 395 | GCC_DYNAMIC_NO_PIC = NO; 396 | GCC_OPTIMIZATION_LEVEL = 0; 397 | GCC_PREPROCESSOR_DEFINITIONS = ( 398 | "DEBUG=1", 399 | "$(inherited)", 400 | ); 401 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 409 | ONLY_ACTIVE_ARCH = YES; 410 | SDKROOT = iphoneos; 411 | }; 412 | name = Debug; 413 | }; 414 | AF24722818B30BAD00614655 /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ALWAYS_SEARCH_USER_PATHS = NO; 418 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 419 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 420 | CLANG_CXX_LIBRARY = "libc++"; 421 | CLANG_ENABLE_MODULES = YES; 422 | CLANG_ENABLE_OBJC_ARC = YES; 423 | CLANG_WARN_BOOL_CONVERSION = YES; 424 | CLANG_WARN_CONSTANT_CONVERSION = YES; 425 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 426 | CLANG_WARN_EMPTY_BODY = YES; 427 | CLANG_WARN_ENUM_CONVERSION = YES; 428 | CLANG_WARN_INT_CONVERSION = YES; 429 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 432 | COPY_PHASE_STRIP = YES; 433 | ENABLE_NS_ASSERTIONS = NO; 434 | GCC_C_LANGUAGE_STANDARD = gnu99; 435 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 436 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 437 | GCC_WARN_UNDECLARED_SELECTOR = YES; 438 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 439 | GCC_WARN_UNUSED_FUNCTION = YES; 440 | GCC_WARN_UNUSED_VARIABLE = YES; 441 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 442 | SDKROOT = iphoneos; 443 | VALIDATE_PRODUCT = YES; 444 | }; 445 | name = Release; 446 | }; 447 | AF24722A18B30BAD00614655 /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 451 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 452 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 453 | GCC_PREFIX_HEADER = "SECollectionViewFlowLayout/SECollectionViewFlowLayout-Prefix.pch"; 454 | INFOPLIST_FILE = "SECollectionViewFlowLayout/SECollectionViewFlowLayout-Info.plist"; 455 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 456 | PRODUCT_NAME = "$(TARGET_NAME)"; 457 | WRAPPER_EXTENSION = app; 458 | }; 459 | name = Debug; 460 | }; 461 | AF24722B18B30BAD00614655 /* Release */ = { 462 | isa = XCBuildConfiguration; 463 | buildSettings = { 464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 465 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 466 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 467 | GCC_PREFIX_HEADER = "SECollectionViewFlowLayout/SECollectionViewFlowLayout-Prefix.pch"; 468 | INFOPLIST_FILE = "SECollectionViewFlowLayout/SECollectionViewFlowLayout-Info.plist"; 469 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | WRAPPER_EXTENSION = app; 472 | }; 473 | name = Release; 474 | }; 475 | AF24722D18B30BAD00614655 /* Debug */ = { 476 | isa = XCBuildConfiguration; 477 | buildSettings = { 478 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 479 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SECollectionViewFlowLayout.app/SECollectionViewFlowLayout"; 480 | FRAMEWORK_SEARCH_PATHS = ( 481 | "$(SDKROOT)/Developer/Library/Frameworks", 482 | "$(inherited)", 483 | "$(DEVELOPER_FRAMEWORKS_DIR)", 484 | ); 485 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 486 | GCC_PREFIX_HEADER = "SECollectionViewFlowLayout/SECollectionViewFlowLayout-Prefix.pch"; 487 | GCC_PREPROCESSOR_DEFINITIONS = ( 488 | "DEBUG=1", 489 | "$(inherited)", 490 | ); 491 | INFOPLIST_FILE = "SECollectionViewFlowLayoutTests/SECollectionViewFlowLayoutTests-Info.plist"; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | TEST_HOST = "$(BUNDLE_LOADER)"; 494 | WRAPPER_EXTENSION = xctest; 495 | }; 496 | name = Debug; 497 | }; 498 | AF24722E18B30BAD00614655 /* Release */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 502 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SECollectionViewFlowLayout.app/SECollectionViewFlowLayout"; 503 | FRAMEWORK_SEARCH_PATHS = ( 504 | "$(SDKROOT)/Developer/Library/Frameworks", 505 | "$(inherited)", 506 | "$(DEVELOPER_FRAMEWORKS_DIR)", 507 | ); 508 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 509 | GCC_PREFIX_HEADER = "SECollectionViewFlowLayout/SECollectionViewFlowLayout-Prefix.pch"; 510 | INFOPLIST_FILE = "SECollectionViewFlowLayoutTests/SECollectionViewFlowLayoutTests-Info.plist"; 511 | PRODUCT_NAME = "$(TARGET_NAME)"; 512 | TEST_HOST = "$(BUNDLE_LOADER)"; 513 | WRAPPER_EXTENSION = xctest; 514 | }; 515 | name = Release; 516 | }; 517 | /* End XCBuildConfiguration section */ 518 | 519 | /* Begin XCConfigurationList section */ 520 | AF2471F218B30BAD00614655 /* Build configuration list for PBXProject "SECollectionViewFlowLayout" */ = { 521 | isa = XCConfigurationList; 522 | buildConfigurations = ( 523 | AF24722718B30BAD00614655 /* Debug */, 524 | AF24722818B30BAD00614655 /* Release */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | AF24722918B30BAD00614655 /* Build configuration list for PBXNativeTarget "SECollectionViewFlowLayout" */ = { 530 | isa = XCConfigurationList; 531 | buildConfigurations = ( 532 | AF24722A18B30BAD00614655 /* Debug */, 533 | AF24722B18B30BAD00614655 /* Release */, 534 | ); 535 | defaultConfigurationIsVisible = 0; 536 | defaultConfigurationName = Release; 537 | }; 538 | AF24722C18B30BAD00614655 /* Build configuration list for PBXNativeTarget "SECollectionViewFlowLayoutTests" */ = { 539 | isa = XCConfigurationList; 540 | buildConfigurations = ( 541 | AF24722D18B30BAD00614655 /* Debug */, 542 | AF24722E18B30BAD00614655 /* Release */, 543 | ); 544 | defaultConfigurationIsVisible = 0; 545 | defaultConfigurationName = Release; 546 | }; 547 | /* End XCConfigurationList section */ 548 | }; 549 | rootObject = AF2471EF18B30BAD00614655 /* Project object */; 550 | } 551 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout.xcodeproj/xcuserdata/ChrisW.xcuserdatad/xcschemes/SECollectionViewFlowLayout.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout.xcodeproj/xcuserdata/ChrisW.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SECollectionViewFlowLayout.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | AF2471F618B30BAD00614655 16 | 17 | primary 18 | 19 | 20 | AF24721718B30BAD00614655 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/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 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 87 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 111 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 135 | 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 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/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" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/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 | } -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionCheckmarkView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionCheckmarkView.h 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2014/01/01. 6 | // Copyright (c) 2014年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QBAssetsCollectionCheckmarkView : UIView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionCheckmarkView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionCheckmarkView.m 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2014/01/01. 6 | // Copyright (c) 2014年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBAssetsCollectionCheckmarkView.h" 10 | 11 | @implementation QBAssetsCollectionCheckmarkView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | 17 | if (self) { 18 | // View settings 19 | self.backgroundColor = [UIColor clearColor]; 20 | } 21 | 22 | return self; 23 | } 24 | 25 | - (CGSize)sizeThatFits:(CGSize)size 26 | { 27 | return CGSizeMake(24.0, 24.0); 28 | } 29 | 30 | - (void)drawRect:(CGRect)rect 31 | { 32 | CGContextRef context = UIGraphicsGetCurrentContext(); 33 | 34 | // Border 35 | CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0); 36 | CGContextFillEllipseInRect(context, self.bounds); 37 | 38 | // Body 39 | CGContextSetRGBFillColor(context, 20.0/255.0, 111.0/255.0, 223.0/255.0, 1.0); 40 | CGContextFillEllipseInRect(context, CGRectInset(self.bounds, 1.0, 1.0)); 41 | 42 | // Checkmark 43 | CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); 44 | CGContextSetLineWidth(context, 1.2); 45 | 46 | CGContextMoveToPoint(context, 6.0, 12.0); 47 | CGContextAddLineToPoint(context, 10.0, 16.0); 48 | CGContextAddLineToPoint(context, 18.0, 8.0); 49 | 50 | CGContextStrokePath(context); 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionFooterView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionFooterView.h 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/31. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QBAssetsCollectionFooterView : UICollectionReusableView 12 | 13 | @property (nonatomic, strong, readonly) UILabel *textLabel; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionFooterView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionFooterView.m 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/31. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBAssetsCollectionFooterView.h" 10 | 11 | @interface QBAssetsCollectionFooterView () 12 | 13 | @property (nonatomic, strong, readwrite) UILabel *textLabel; 14 | 15 | @end 16 | 17 | @implementation QBAssetsCollectionFooterView 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame 20 | { 21 | self = [super initWithFrame:frame]; 22 | 23 | if (self) { 24 | // Create a label 25 | UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 26 | textLabel.font = [UIFont systemFontOfSize:17]; 27 | textLabel.textColor = [UIColor blackColor]; 28 | textLabel.textAlignment = NSTextAlignmentCenter; 29 | 30 | [self addSubview:textLabel]; 31 | self.textLabel = textLabel; 32 | } 33 | 34 | return self; 35 | } 36 | 37 | - (void)layoutSubviews 38 | { 39 | [super layoutSubviews]; 40 | 41 | // Layout text label 42 | self.textLabel.frame = CGRectMake(0, 43 | (self.bounds.size.height - 21.0) / 2.0, 44 | self.bounds.size.width, 45 | 21.0); 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionOverlayView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionOverlayView.h 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2014/01/01. 6 | // Copyright (c) 2014年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QBAssetsCollectionOverlayView : UIView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionOverlayView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionOverlayView.m 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2014/01/01. 6 | // Copyright (c) 2014年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBAssetsCollectionOverlayView.h" 10 | #import 11 | 12 | // Views 13 | #import "QBAssetsCollectionCheckmarkView.h" 14 | 15 | @interface QBAssetsCollectionOverlayView () 16 | 17 | @end 18 | 19 | @implementation QBAssetsCollectionOverlayView 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame 22 | { 23 | self = [super initWithFrame:frame]; 24 | 25 | if (self) { 26 | // View settings 27 | self.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.5]; 28 | 29 | // Create a checkmark view 30 | QBAssetsCollectionCheckmarkView *checkmarkView = [[QBAssetsCollectionCheckmarkView alloc] initWithFrame:CGRectMake(self.bounds.size.width - (4.0 + 24.0), self.bounds.size.height - (4.0 + 24.0), 24.0, 24.0)]; 31 | checkmarkView.autoresizingMask = UIViewAutoresizingNone; 32 | 33 | [self addSubview:checkmarkView]; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionViewCell.h 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/31. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface QBAssetsCollectionViewCell : UICollectionViewCell 13 | 14 | @property (nonatomic, strong) ALAsset *asset; 15 | @property (nonatomic, assign) BOOL showsOverlayViewWhenSelected; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionViewCell.m 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/31. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBAssetsCollectionViewCell.h" 10 | 11 | // Views 12 | #import "QBAssetsCollectionOverlayView.h" 13 | 14 | @interface QBAssetsCollectionViewCell () 15 | 16 | @property (nonatomic, strong) UIImageView *imageView; 17 | @property (nonatomic, strong) QBAssetsCollectionOverlayView *overlayView; 18 | 19 | @end 20 | 21 | @implementation QBAssetsCollectionViewCell 22 | 23 | - (instancetype)initWithFrame:(CGRect)frame 24 | { 25 | self = [super initWithFrame:frame]; 26 | 27 | if (self) { 28 | self.showsOverlayViewWhenSelected = YES; 29 | 30 | // Create a image view 31 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.contentView.bounds]; 32 | imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 33 | 34 | [self.contentView addSubview:imageView]; 35 | self.imageView = imageView; 36 | 37 | self.overlayView = [[QBAssetsCollectionOverlayView alloc] initWithFrame:self.contentView.bounds]; 38 | self.overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 39 | 40 | [self.contentView addSubview:self.overlayView]; 41 | self.overlayView.alpha = 0.f; 42 | } 43 | 44 | return self; 45 | } 46 | 47 | - (void)setSelected:(BOOL)selected 48 | { 49 | [super setSelected:selected]; 50 | 51 | // Show/hide overlay view 52 | if (selected && self.showsOverlayViewWhenSelected) { 53 | // [self hideOverlayView]; 54 | [self showOverlayView]; 55 | } else { 56 | [self hideOverlayView]; 57 | } 58 | } 59 | 60 | - (void)showOverlayView 61 | { 62 | self.overlayView.alpha = 1.f; 63 | } 64 | 65 | - (void)hideOverlayView 66 | { 67 | self.overlayView.alpha = 0.f; 68 | } 69 | 70 | 71 | #pragma mark - Accessors 72 | 73 | - (void)setAsset:(ALAsset *)asset 74 | { 75 | _asset = asset; 76 | 77 | // Update view 78 | self.imageView.image = [UIImage imageWithCGImage:[asset thumbnail]]; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionViewController.h 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/31. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | // ViewControllers 13 | #import "QBImagePickerController.h" 14 | 15 | @class QBAssetsCollectionViewController; 16 | 17 | @protocol QBAssetsCollectionViewControllerDelegate 18 | 19 | @optional 20 | - (void)assetsCollectionViewController:(QBAssetsCollectionViewController *)assetsCollectionViewController didSelectAsset:(ALAsset *)asset; 21 | - (void)assetsCollectionViewController:(QBAssetsCollectionViewController *)assetsCollectionViewController didDeselectAsset:(ALAsset *)asset; 22 | - (void)assetsCollectionViewControllerDidFinishSelection:(QBAssetsCollectionViewController *)assetsCollectionViewController; 23 | 24 | @end 25 | 26 | @interface QBAssetsCollectionViewController : UICollectionViewController 27 | 28 | @property (nonatomic, weak) QBImagePickerController *imagePickerController; 29 | 30 | @property (nonatomic, weak) id delegate; 31 | @property (nonatomic, strong) ALAssetsGroup *assetsGroup; 32 | @property (nonatomic, assign) QBImagePickerControllerFilterType filterType; 33 | @property (nonatomic, assign) BOOL allowsMultipleSelection; 34 | @property (nonatomic, assign) NSUInteger minimumNumberOfSelection; 35 | @property (nonatomic, assign) NSUInteger maximumNumberOfSelection; 36 | 37 | - (void)selectAssetHavingURL:(NSURL *)URL; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBAssetsCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBAssetsCollectionViewController.m 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/31. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBAssetsCollectionViewController.h" 10 | 11 | // Views 12 | #import "QBAssetsCollectionViewCell.h" 13 | #import "QBAssetsCollectionFooterView.h" 14 | 15 | @interface QBAssetsCollectionViewController () 16 | 17 | @property (nonatomic, strong) NSMutableArray *assets; 18 | 19 | @property (nonatomic, assign) NSInteger numberOfPhotos; 20 | @property (nonatomic, assign) NSInteger numberOfVideos; 21 | 22 | @end 23 | 24 | @implementation QBAssetsCollectionViewController 25 | 26 | - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout 27 | { 28 | self = [super initWithCollectionViewLayout:layout]; 29 | 30 | if (self) { 31 | // View settings 32 | self.collectionView.backgroundColor = [UIColor whiteColor]; 33 | 34 | // Register cell class 35 | [self.collectionView registerClass:[QBAssetsCollectionViewCell class] 36 | forCellWithReuseIdentifier:@"AssetsCell"]; 37 | [self.collectionView registerClass:[QBAssetsCollectionFooterView class] 38 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 39 | withReuseIdentifier:@"FooterView"]; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | - (void)viewWillAppear:(BOOL)animated 46 | { 47 | [super viewWillAppear:animated]; 48 | 49 | // Scroll to bottom --- iOS 7 differences 50 | CGFloat topInset; 51 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 52 | topInset = ((self.edgesForExtendedLayout && UIRectEdgeTop) && (self.collectionView.contentInset.top == 0)) ? (20.0 + 44.0) : 0.0; 53 | } else { 54 | topInset = (self.collectionView.contentInset.top == 0) ? (20.0 + 44.0) : 0.0; 55 | } 56 | 57 | [self.collectionView setContentOffset:CGPointMake(0, self.collectionView.collectionViewLayout.collectionViewContentSize.height - self.collectionView.frame.size.height + topInset) 58 | animated:NO]; 59 | 60 | // Validation 61 | if (self.allowsMultipleSelection) { 62 | self.navigationItem.rightBarButtonItem.enabled = [self validateNumberOfSelections:self.imagePickerController.selectedAssetURLs.count]; 63 | } 64 | } 65 | 66 | #pragma mark - Accessors 67 | 68 | - (void)setFilterType:(QBImagePickerControllerFilterType)filterType 69 | { 70 | _filterType = filterType; 71 | 72 | // Set assets filter 73 | [self.assetsGroup setAssetsFilter:ALAssetsFilterFromQBImagePickerControllerFilterType(self.filterType)]; 74 | } 75 | 76 | - (void)setAssetsGroup:(ALAssetsGroup *)assetsGroup 77 | { 78 | _assetsGroup = assetsGroup; 79 | 80 | // Set title 81 | self.title = [self.assetsGroup valueForProperty:ALAssetsGroupPropertyName]; 82 | 83 | // Get the number of photos and videos 84 | [self.assetsGroup setAssetsFilter:[ALAssetsFilter allPhotos]]; 85 | self.numberOfPhotos = self.assetsGroup.numberOfAssets; 86 | 87 | [self.assetsGroup setAssetsFilter:[ALAssetsFilter allVideos]]; 88 | self.numberOfVideos = self.assetsGroup.numberOfAssets; 89 | 90 | // Set assets filter 91 | [self.assetsGroup setAssetsFilter:ALAssetsFilterFromQBImagePickerControllerFilterType(self.filterType)]; 92 | 93 | // Load assets 94 | self.assets = [NSMutableArray array]; 95 | 96 | __weak typeof(self) weakSelf = self; 97 | [self.assetsGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { 98 | if (result) { 99 | [weakSelf.assets addObject:result]; 100 | } 101 | }]; 102 | 103 | // Update view 104 | [self.collectionView reloadData]; 105 | } 106 | 107 | - (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection 108 | { 109 | self.collectionView.allowsMultipleSelection = allowsMultipleSelection; 110 | 111 | // Show/hide done button 112 | if (allowsMultipleSelection) { 113 | UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(done:)]; 114 | [self.navigationItem setRightBarButtonItem:doneButton animated:NO]; 115 | } else { 116 | [self.navigationItem setRightBarButtonItem:nil animated:NO]; 117 | } 118 | } 119 | 120 | - (BOOL)allowsMultipleSelection 121 | { 122 | return self.collectionView.allowsMultipleSelection; 123 | } 124 | 125 | 126 | #pragma mark - Actions 127 | 128 | - (void)done:(id)sender 129 | { 130 | // Delegate 131 | if (self.delegate && [self.delegate respondsToSelector:@selector(assetsCollectionViewControllerDidFinishSelection:)]) { 132 | [self.delegate assetsCollectionViewControllerDidFinishSelection:self]; 133 | } 134 | } 135 | 136 | #pragma mark - Managing Selection 137 | 138 | - (void)selectAssetHavingURL:(NSURL *)URL 139 | { 140 | for (NSInteger i = 0; i < self.assets.count; i++) { 141 | ALAsset *asset = [self.assets objectAtIndex:i]; 142 | NSURL *assetURL = [asset valueForProperty:ALAssetPropertyAssetURL]; 143 | 144 | if ([assetURL isEqual:URL]) { 145 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; 146 | [self.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; 147 | 148 | return; 149 | } 150 | } 151 | } 152 | 153 | 154 | #pragma mark - Validating Selections 155 | 156 | - (BOOL)validateNumberOfSelections:(NSUInteger)numberOfSelections 157 | { 158 | NSUInteger minimumNumberOfSelection = MAX(1, self.minimumNumberOfSelection); 159 | BOOL qualifiesMinimumNumberOfSelection = (numberOfSelections >= minimumNumberOfSelection); 160 | 161 | BOOL qualifiesMaximumNumberOfSelection = YES; 162 | if (minimumNumberOfSelection <= self.maximumNumberOfSelection) { 163 | qualifiesMaximumNumberOfSelection = (numberOfSelections <= self.maximumNumberOfSelection); 164 | } 165 | 166 | return (qualifiesMinimumNumberOfSelection && qualifiesMaximumNumberOfSelection); 167 | } 168 | 169 | - (BOOL)validateMaximumNumberOfSelections:(NSUInteger)numberOfSelections 170 | { 171 | NSUInteger minimumNumberOfSelection = MAX(1, self.minimumNumberOfSelection); 172 | 173 | if (minimumNumberOfSelection <= self.maximumNumberOfSelection) { 174 | return (numberOfSelections <= self.maximumNumberOfSelection); 175 | } 176 | 177 | return YES; 178 | } 179 | 180 | #pragma mark - UICollectionViewDataSource 181 | 182 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 183 | { 184 | return 1; 185 | } 186 | 187 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 188 | { 189 | return self.assetsGroup.numberOfAssets; 190 | } 191 | 192 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 193 | { 194 | QBAssetsCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AssetsCell" forIndexPath:indexPath]; 195 | cell.showsOverlayViewWhenSelected = self.allowsMultipleSelection; 196 | 197 | ALAsset *asset = [self.assets objectAtIndex:indexPath.row]; 198 | cell.asset = asset; 199 | 200 | return cell; 201 | } 202 | 203 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section 204 | { 205 | return CGSizeMake(collectionView.bounds.size.width, 46.0); 206 | } 207 | 208 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 209 | { 210 | if (kind == UICollectionElementKindSectionFooter) { 211 | QBAssetsCollectionFooterView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter 212 | withReuseIdentifier:@"FooterView" 213 | forIndexPath:indexPath]; 214 | 215 | switch (self.filterType) { 216 | case QBImagePickerControllerFilterTypeNone: 217 | footerView.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"format_photos_and_videos", 218 | @"QBImagePickerController", 219 | nil), 220 | self.numberOfPhotos, 221 | self.numberOfVideos 222 | ]; 223 | break; 224 | 225 | case QBImagePickerControllerFilterTypePhotos: 226 | footerView.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"format_photos", 227 | @"QBImagePickerController", 228 | nil), 229 | self.numberOfPhotos 230 | ]; 231 | break; 232 | 233 | case QBImagePickerControllerFilterTypeVideos: 234 | footerView.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"format_videos", 235 | @"QBImagePickerController", 236 | nil), 237 | self.numberOfVideos 238 | ]; 239 | break; 240 | } 241 | 242 | return footerView; 243 | } 244 | 245 | return nil; 246 | } 247 | 248 | 249 | #pragma mark - UICollectionViewDelegateFlowLayout 250 | 251 | //- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 252 | //{ 253 | // if (indexPath.row % 2 == 0) { 254 | // return CGSizeMake(50.5, 50.f); 255 | // } 256 | // 257 | // CGFloat size = (indexPath.row + 1) * 5.f; 258 | // 259 | // return CGSizeMake(size, size); 260 | //} 261 | 262 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section 263 | { 264 | return UIEdgeInsetsMake(2, 2, 2, 2); 265 | } 266 | 267 | - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath 268 | { 269 | return [self validateMaximumNumberOfSelections:(self.imagePickerController.selectedAssetURLs.count + 1)]; 270 | } 271 | 272 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 273 | { 274 | ALAsset *asset = [self.assets objectAtIndex:indexPath.row]; 275 | 276 | // Validation 277 | if (self.allowsMultipleSelection) { 278 | self.navigationItem.rightBarButtonItem.enabled = [self validateNumberOfSelections:(self.imagePickerController.selectedAssetURLs.count + 1)]; 279 | } 280 | 281 | // Delegate 282 | if (self.delegate && [self.delegate respondsToSelector:@selector(assetsCollectionViewController:didSelectAsset:)]) { 283 | [self.delegate assetsCollectionViewController:self didSelectAsset:asset]; 284 | } 285 | } 286 | 287 | - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath 288 | { 289 | ALAsset *asset = [self.assets objectAtIndex:indexPath.row]; 290 | 291 | // Validation 292 | if (self.allowsMultipleSelection) { 293 | self.navigationItem.rightBarButtonItem.enabled = [self validateNumberOfSelections:(self.imagePickerController.selectedAssetURLs.count - 1)]; 294 | } 295 | 296 | // Delegate 297 | if (self.delegate && [self.delegate respondsToSelector:@selector(assetsCollectionViewController:didDeselectAsset:)]) { 298 | [self.delegate assetsCollectionViewController:self didDeselectAsset:asset]; 299 | } 300 | } 301 | 302 | @end 303 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBImagePickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBImagePickerController.h 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/30. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSUInteger, QBImagePickerControllerFilterType) { 13 | QBImagePickerControllerFilterTypeNone, 14 | QBImagePickerControllerFilterTypePhotos, 15 | QBImagePickerControllerFilterTypeVideos 16 | }; 17 | 18 | UIKIT_EXTERN ALAssetsFilter * ALAssetsFilterFromQBImagePickerControllerFilterType(QBImagePickerControllerFilterType type); 19 | 20 | @class QBImagePickerController; 21 | 22 | @protocol QBImagePickerControllerDelegate 23 | 24 | @optional 25 | - (void)imagePickerController:(QBImagePickerController *)imagePickerController didSelectAsset:(ALAsset *)asset; 26 | - (void)imagePickerController:(QBImagePickerController *)imagePickerController didSelectAssets:(NSArray *)assets; 27 | - (void)imagePickerControllerDidCancel:(QBImagePickerController *)imagePickerController; 28 | 29 | @end 30 | 31 | @interface QBImagePickerController : UITableViewController 32 | 33 | @property (nonatomic, strong, readonly) ALAssetsLibrary *assetsLibrary; 34 | @property (nonatomic, copy, readonly) NSArray *assetsGroups; 35 | @property (nonatomic, strong, readonly) NSMutableSet *selectedAssetURLs; 36 | 37 | @property (nonatomic, weak) id delegate; 38 | @property (nonatomic, copy) NSArray *groupTypes; 39 | @property (nonatomic, assign) QBImagePickerControllerFilterType filterType; 40 | @property (nonatomic, assign) BOOL showsCancelButton; 41 | @property (nonatomic, assign) BOOL allowsMultipleSelection; 42 | @property (nonatomic, assign) NSUInteger minimumNumberOfSelection; 43 | @property (nonatomic, assign) NSUInteger maximumNumberOfSelection; 44 | 45 | + (BOOL)isAccessible; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBImagePickerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBImagePickerController.m 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/30. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBImagePickerController.h" 10 | #import 11 | 12 | // Views 13 | #import "QBImagePickerGroupCell.h" 14 | #import "SECollectionViewFlowLayout.h" 15 | 16 | // ViewControllers 17 | #import "QBAssetsCollectionViewController.h" 18 | 19 | ALAssetsFilter * ALAssetsFilterFromQBImagePickerControllerFilterType(QBImagePickerControllerFilterType type) { 20 | switch (type) { 21 | case QBImagePickerControllerFilterTypeNone: 22 | return [ALAssetsFilter allAssets]; 23 | break; 24 | 25 | case QBImagePickerControllerFilterTypePhotos: 26 | return [ALAssetsFilter allPhotos]; 27 | break; 28 | 29 | case QBImagePickerControllerFilterTypeVideos: 30 | return [ALAssetsFilter allVideos]; 31 | break; 32 | } 33 | } 34 | 35 | @interface QBImagePickerController () 36 | 37 | @property (nonatomic, strong, readwrite) ALAssetsLibrary *assetsLibrary; 38 | @property (nonatomic, copy, readwrite) NSArray *assetsGroups; 39 | @property (nonatomic, strong, readwrite) NSMutableSet *selectedAssetURLs; 40 | 41 | @end 42 | 43 | @implementation QBImagePickerController 44 | 45 | + (BOOL)isAccessible 46 | { 47 | return ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] && 48 | [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]); 49 | } 50 | 51 | - (instancetype)initWithStyle:(UITableViewStyle)style 52 | { 53 | self = [super initWithStyle:UITableViewStylePlain]; 54 | 55 | if (self) { 56 | // Property settings 57 | self.selectedAssetURLs = [NSMutableSet set]; 58 | 59 | self.groupTypes = @[ 60 | @(ALAssetsGroupSavedPhotos), 61 | @(ALAssetsGroupPhotoStream), 62 | @(ALAssetsGroupAlbum) 63 | ]; 64 | self.filterType = QBImagePickerControllerFilterTypeNone; 65 | self.showsCancelButton = YES; 66 | 67 | // View settings 68 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 69 | 70 | // Create assets library instance 71 | ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init]; 72 | self.assetsLibrary = assetsLibrary; 73 | 74 | // Register cell classes 75 | [self.tableView registerClass:[QBImagePickerGroupCell class] forCellReuseIdentifier:@"GroupCell"]; 76 | } 77 | 78 | return self; 79 | } 80 | 81 | - (void)viewDidLoad 82 | { 83 | [super viewDidLoad]; 84 | 85 | // View controller settings 86 | self.title = NSLocalizedStringFromTable(@"title", @"QBImagePickerController", nil); 87 | } 88 | 89 | - (void)viewWillAppear:(BOOL)animated 90 | { 91 | [super viewWillAppear:animated]; 92 | 93 | // Load assets groups 94 | [self loadAssetsGroupsWithTypes:self.groupTypes 95 | completion:^(NSArray *assetsGroups) { 96 | self.assetsGroups = assetsGroups; 97 | 98 | [self.tableView reloadData]; 99 | }]; 100 | 101 | // Validation 102 | self.navigationItem.rightBarButtonItem.enabled = [self validateNumberOfSelections:self.selectedAssetURLs.count]; 103 | } 104 | 105 | 106 | #pragma mark - Accessors 107 | 108 | - (void)setShowsCancelButton:(BOOL)showsCancelButton 109 | { 110 | _showsCancelButton = showsCancelButton; 111 | 112 | // Show/hide cancel button 113 | if (showsCancelButton) { 114 | UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)]; 115 | [self.navigationItem setLeftBarButtonItem:cancelButton animated:NO]; 116 | } else { 117 | [self.navigationItem setLeftBarButtonItem:nil animated:NO]; 118 | } 119 | } 120 | 121 | - (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection 122 | { 123 | _allowsMultipleSelection = allowsMultipleSelection; 124 | 125 | // Show/hide done button 126 | if (allowsMultipleSelection) { 127 | UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(done:)]; 128 | [self.navigationItem setRightBarButtonItem:doneButton animated:NO]; 129 | } else { 130 | [self.navigationItem setRightBarButtonItem:nil animated:NO]; 131 | } 132 | } 133 | 134 | 135 | #pragma mark - Actions 136 | 137 | - (void)done:(id)sender 138 | { 139 | [self passSelectedAssetsToDelegate]; 140 | } 141 | 142 | - (void)cancel:(id)sender 143 | { 144 | // Delegate 145 | if (self.delegate && [self.delegate respondsToSelector:@selector(imagePickerControllerDidCancel:)]) { 146 | [self.delegate imagePickerControllerDidCancel:self]; 147 | } 148 | } 149 | 150 | 151 | #pragma mark - Validating Selections 152 | 153 | - (BOOL)validateNumberOfSelections:(NSUInteger)numberOfSelections 154 | { 155 | // Check the number of selected assets 156 | NSUInteger minimumNumberOfSelection = MAX(1, self.minimumNumberOfSelection); 157 | BOOL qualifiesMinimumNumberOfSelection = (numberOfSelections >= minimumNumberOfSelection); 158 | 159 | BOOL qualifiesMaximumNumberOfSelection = YES; 160 | if (minimumNumberOfSelection <= self.maximumNumberOfSelection) { 161 | qualifiesMaximumNumberOfSelection = (numberOfSelections <= self.maximumNumberOfSelection); 162 | } 163 | 164 | return (qualifiesMinimumNumberOfSelection && qualifiesMaximumNumberOfSelection); 165 | } 166 | 167 | 168 | #pragma mark - Managing Assets 169 | 170 | - (void)loadAssetsGroupsWithTypes:(NSArray *)types completion:(void (^)(NSArray *assetsGroups))completion 171 | { 172 | __block NSMutableArray *assetsGroups = [NSMutableArray array]; 173 | __block NSUInteger numberOfFinishedTypes = 0; 174 | 175 | for (NSNumber *type in types) { 176 | __weak typeof(self) weakSelf = self; 177 | 178 | [self.assetsLibrary enumerateGroupsWithTypes:[type unsignedIntegerValue] 179 | usingBlock:^(ALAssetsGroup *assetsGroup, BOOL *stop) { 180 | if (assetsGroup) { 181 | // Filter the assets group 182 | [assetsGroup setAssetsFilter:ALAssetsFilterFromQBImagePickerControllerFilterType(weakSelf.filterType)]; 183 | 184 | if (assetsGroup.numberOfAssets > 0) { 185 | // Add assets group 186 | [assetsGroups addObject:assetsGroup]; 187 | } 188 | } else { 189 | numberOfFinishedTypes++; 190 | } 191 | 192 | // Check if the loading finished 193 | if (numberOfFinishedTypes == types.count) { 194 | // Sort assets groups 195 | NSArray *sortedAssetsGroups = [self sortAssetsGroups:(NSArray *)assetsGroups typesOrder:types]; 196 | 197 | // Call completion block 198 | if (completion) { 199 | completion(sortedAssetsGroups); 200 | } 201 | } 202 | } failureBlock:^(NSError *error) { 203 | NSLog(@"Error: %@", [error localizedDescription]); 204 | }]; 205 | } 206 | } 207 | 208 | - (NSArray *)sortAssetsGroups:(NSArray *)assetsGroups typesOrder:(NSArray *)typesOrder 209 | { 210 | NSMutableArray *sortedAssetsGroups = [NSMutableArray array]; 211 | 212 | for (ALAssetsGroup *assetsGroup in assetsGroups) { 213 | if (sortedAssetsGroups.count == 0) { 214 | [sortedAssetsGroups addObject:assetsGroup]; 215 | continue; 216 | } 217 | 218 | ALAssetsGroupType assetsGroupType = [[assetsGroup valueForProperty:ALAssetsGroupPropertyType] unsignedIntegerValue]; 219 | NSUInteger indexOfAssetsGroupType = [typesOrder indexOfObject:@(assetsGroupType)]; 220 | 221 | for (NSInteger i = 0; i <= sortedAssetsGroups.count; i++) { 222 | if (i == sortedAssetsGroups.count) { 223 | [sortedAssetsGroups addObject:assetsGroup]; 224 | break; 225 | } 226 | 227 | ALAssetsGroup *sortedAssetsGroup = [sortedAssetsGroups objectAtIndex:i]; 228 | ALAssetsGroupType sortedAssetsGroupType = [[sortedAssetsGroup valueForProperty:ALAssetsGroupPropertyType] unsignedIntegerValue]; 229 | NSUInteger indexOfSortedAssetsGroupType = [typesOrder indexOfObject:@(sortedAssetsGroupType)]; 230 | 231 | if (indexOfAssetsGroupType < indexOfSortedAssetsGroupType) { 232 | [sortedAssetsGroups insertObject:assetsGroup atIndex:i]; 233 | break; 234 | } 235 | } 236 | } 237 | 238 | return [sortedAssetsGroups copy]; 239 | } 240 | 241 | - (void)passSelectedAssetsToDelegate 242 | { 243 | // Load assets from URLs 244 | __block NSMutableArray *assets = [NSMutableArray array]; 245 | 246 | for (NSURL *selectedAssetURL in self.selectedAssetURLs) { 247 | __weak typeof(self) weakSelf = self; 248 | [self.assetsLibrary assetForURL:selectedAssetURL 249 | resultBlock:^(ALAsset *asset) { 250 | // Add asset 251 | [assets addObject:asset]; 252 | 253 | // Check if the loading finished 254 | if (assets.count == weakSelf.selectedAssetURLs.count) { 255 | // Delegate 256 | if (self.delegate && [self.delegate respondsToSelector:@selector(imagePickerController:didSelectAssets:)]) { 257 | [self.delegate imagePickerController:self didSelectAssets:[assets copy]]; 258 | } 259 | } 260 | } failureBlock:^(NSError *error) { 261 | NSLog(@"Error: %@", [error localizedDescription]); 262 | }]; 263 | } 264 | } 265 | 266 | 267 | #pragma mark - UITableViewDataSource 268 | 269 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 270 | { 271 | return 1; 272 | } 273 | 274 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 275 | { 276 | return self.assetsGroups.count; 277 | } 278 | 279 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 280 | { 281 | QBImagePickerGroupCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GroupCell" forIndexPath:indexPath]; 282 | 283 | ALAssetsGroup *assetsGroup = [self.assetsGroups objectAtIndex:indexPath.row]; 284 | cell.assetsGroup = assetsGroup; 285 | 286 | return cell; 287 | } 288 | 289 | 290 | #pragma mark - UITableViewDelegate 291 | 292 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 293 | { 294 | return 86.0; 295 | } 296 | 297 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 298 | { 299 | QBAssetsCollectionViewController *assetsCollectionViewController = [[QBAssetsCollectionViewController alloc] initWithCollectionViewLayout:[SECollectionViewFlowLayout layoutWithAutoSelectRows:YES panToDeselect:YES autoSelectCellsBetweenTouches:YES]]; 300 | assetsCollectionViewController.imagePickerController = self; 301 | assetsCollectionViewController.filterType = self.filterType; 302 | assetsCollectionViewController.allowsMultipleSelection = self.allowsMultipleSelection; 303 | assetsCollectionViewController.minimumNumberOfSelection = self.minimumNumberOfSelection; 304 | assetsCollectionViewController.maximumNumberOfSelection = self.maximumNumberOfSelection; 305 | 306 | ALAssetsGroup *assetsGroup = [self.assetsGroups objectAtIndex:indexPath.row]; 307 | assetsCollectionViewController.delegate = self; 308 | assetsCollectionViewController.assetsGroup = assetsGroup; 309 | 310 | for (NSURL *assetURL in self.selectedAssetURLs) { 311 | [assetsCollectionViewController selectAssetHavingURL:assetURL]; 312 | } 313 | 314 | [self.navigationController pushViewController:assetsCollectionViewController animated:YES]; 315 | } 316 | 317 | 318 | #pragma mark - QBAssetsCollectionViewControllerDelegate 319 | 320 | - (void)assetsCollectionViewController:(QBAssetsCollectionViewController *)assetsCollectionViewController didSelectAsset:(ALAsset *)asset 321 | { 322 | if (self.allowsMultipleSelection) { 323 | // Add asset URL 324 | NSURL *assetURL = [asset valueForProperty:ALAssetPropertyAssetURL]; 325 | [self.selectedAssetURLs addObject:assetURL]; 326 | 327 | // Validation 328 | self.navigationItem.rightBarButtonItem.enabled = [self validateNumberOfSelections:self.selectedAssetURLs.count]; 329 | } else { 330 | // Delegate 331 | if (self.delegate && [self.delegate respondsToSelector:@selector(imagePickerController:didSelectAsset:)]) { 332 | [self.delegate imagePickerController:self didSelectAsset:asset]; 333 | } 334 | } 335 | } 336 | 337 | - (void)assetsCollectionViewController:(QBAssetsCollectionViewController *)assetsCollectionViewController didDeselectAsset:(ALAsset *)asset 338 | { 339 | if (self.allowsMultipleSelection) { 340 | // Remove asset URL 341 | NSURL *assetURL = [asset valueForProperty:ALAssetPropertyAssetURL]; 342 | [self.selectedAssetURLs removeObject:assetURL]; 343 | 344 | // Validation 345 | self.navigationItem.rightBarButtonItem.enabled = [self validateNumberOfSelections:self.selectedAssetURLs.count]; 346 | } 347 | } 348 | 349 | - (void)assetsCollectionViewControllerDidFinishSelection:(QBAssetsCollectionViewController *)assetsCollectionViewController 350 | { 351 | [self passSelectedAssetsToDelegate]; 352 | } 353 | 354 | @end 355 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBImagePickerGroupCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBImagePickerGroupCell.h 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/30. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface QBImagePickerGroupCell : UITableViewCell 13 | 14 | @property (nonatomic, strong) ALAssetsGroup *assetsGroup; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBImagePickerGroupCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBImagePickerGroupCell.m 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/30. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBImagePickerGroupCell.h" 10 | 11 | // Views 12 | #import "QBImagePickerThumbnailView.h" 13 | 14 | @interface QBImagePickerGroupCell () 15 | 16 | @property (nonatomic, strong) QBImagePickerThumbnailView *thumbnailView; 17 | @property (nonatomic, strong) UILabel *nameLabel; 18 | @property (nonatomic, strong) UILabel *countLabel; 19 | 20 | @end 21 | 22 | @implementation QBImagePickerGroupCell 23 | 24 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 25 | { 26 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 27 | 28 | if (self) { 29 | // Cell settings 30 | self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 31 | 32 | // Create thumbnail view 33 | QBImagePickerThumbnailView *thumbnailView = [[QBImagePickerThumbnailView alloc] initWithFrame:CGRectMake(8, 4, 70, 74)]; 34 | thumbnailView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; 35 | 36 | [self.contentView addSubview:thumbnailView]; 37 | self.thumbnailView = thumbnailView; 38 | 39 | // Create name label 40 | UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(8 + 70 + 18, 22, 180, 21)]; 41 | nameLabel.font = [UIFont systemFontOfSize:17]; 42 | nameLabel.textColor = [UIColor blackColor]; 43 | nameLabel.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth; 44 | 45 | [self.contentView addSubview:nameLabel]; 46 | self.nameLabel = nameLabel; 47 | 48 | // Create count label 49 | UILabel *countLabel = [[UILabel alloc] initWithFrame:CGRectMake(8 + 70 + 18, 46, 180, 15)]; 50 | countLabel.font = [UIFont systemFontOfSize:12]; 51 | countLabel.textColor = [UIColor blackColor]; 52 | countLabel.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth; 53 | 54 | [self.contentView addSubview:countLabel]; 55 | self.countLabel = countLabel; 56 | } 57 | 58 | return self; 59 | } 60 | 61 | 62 | #pragma mark - Accessors 63 | 64 | - (void)setAssetsGroup:(ALAssetsGroup *)assetsGroup 65 | { 66 | _assetsGroup = assetsGroup; 67 | 68 | // Update thumbnail view 69 | self.thumbnailView.assetsGroup = self.assetsGroup; 70 | 71 | // Update label 72 | self.nameLabel.text = [self.assetsGroup valueForProperty:ALAssetsGroupPropertyName]; 73 | self.countLabel.text = [NSString stringWithFormat:@"%ld", (long)self.assetsGroup.numberOfAssets]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBImagePickerThumbnailView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QBImagePickerThumbnailView.h 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/31. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface QBImagePickerThumbnailView : UIView 13 | 14 | @property (nonatomic, strong) ALAssetsGroup *assetsGroup; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/QBImagePickerThumbnailView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QBImagePickerThumbnailView.m 3 | // QBImagePickerController 4 | // 5 | // Created by Tanaka Katsuma on 2013/12/31. 6 | // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved. 7 | // 8 | 9 | #import "QBImagePickerThumbnailView.h" 10 | 11 | @interface QBImagePickerThumbnailView () 12 | 13 | @property (nonatomic, copy) NSArray *thumbnailImages; 14 | @end 15 | 16 | @implementation QBImagePickerThumbnailView 17 | 18 | - (instancetype)initWithFrame:(CGRect)frame 19 | { 20 | self = [super initWithFrame:frame]; 21 | 22 | if (self) { 23 | self.backgroundColor = [UIColor clearColor]; 24 | } 25 | 26 | return self; 27 | } 28 | 29 | - (CGSize)sizeThatFits:(CGSize)size 30 | { 31 | return CGSizeMake(70.0, 74.0); 32 | } 33 | 34 | - (void)drawRect:(CGRect)rect 35 | { 36 | [super drawRect:rect]; 37 | 38 | CGContextRef context = UIGraphicsGetCurrentContext(); 39 | CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0); 40 | 41 | if (self.thumbnailImages.count == 3) { 42 | UIImage *thumbnailImage = [self.thumbnailImages objectAtIndex:2]; 43 | 44 | CGRect thumbnailImageRect = CGRectMake(4.0, 0, 62.0, 62.0); 45 | CGContextFillRect(context, thumbnailImageRect); 46 | [thumbnailImage drawInRect:CGRectInset(thumbnailImageRect, 0.5, 0.5)]; 47 | } 48 | 49 | if (self.thumbnailImages.count >= 2) { 50 | UIImage *thumbnailImage = [self.thumbnailImages objectAtIndex:1]; 51 | 52 | CGRect thumbnailImageRect = CGRectMake(2.0, 2.0, 66.0, 66.0); 53 | CGContextFillRect(context, thumbnailImageRect); 54 | [thumbnailImage drawInRect:CGRectInset(thumbnailImageRect, 0.5, 0.5)]; 55 | } 56 | 57 | UIImage *thumbnailImage = [self.thumbnailImages objectAtIndex:0]; 58 | 59 | CGRect thumbnailImageRect = CGRectMake(0, 4.0, 70.0, 70.0); 60 | CGContextFillRect(context, thumbnailImageRect); 61 | [thumbnailImage drawInRect:CGRectInset(thumbnailImageRect, 0.5, 0.5)]; 62 | } 63 | 64 | 65 | #pragma mark - Accessors 66 | 67 | - (void)setAssetsGroup:(ALAssetsGroup *)assetsGroup 68 | { 69 | _assetsGroup = assetsGroup; 70 | 71 | // Extract three thumbnail images 72 | NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, MIN(3, assetsGroup.numberOfAssets))]; 73 | NSMutableArray *thumbnailImages = [NSMutableArray array]; 74 | [assetsGroup enumerateAssetsAtIndexes:indexes 75 | options:0 76 | usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { 77 | if (result) { 78 | UIImage *thumbnailImage = [UIImage imageWithCGImage:[result thumbnail]]; 79 | [thumbnailImages addObject:thumbnailImage]; 80 | } 81 | }]; 82 | self.thumbnailImages = [thumbnailImages copy]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/SECollectionViewFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // SECollectionViewFlowLayout.h 3 | // SECollectionViewFlowLayout 4 | // 5 | // Created by Chris Wendel on 2014/1/30. 6 | // Copyright (c) 2014 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SECollectionViewFlowLayout : UICollectionViewFlowLayout 12 | 13 | @property (nonatomic) BOOL panToDeselect; // If set to YES, enables the deselecting of cells by panning around on a touch down 14 | @property (nonatomic) BOOL autoSelectRows; // If set to YES, a pan across a row and then down a column will auto-select all cells in each row as you scroll down (used to easily select a lot of cells rather than panning over every cell) 15 | @property (nonatomic) BOOL autoSelectCellsBetweenTouches; // If set to YES, enables auto-selecting all cells between a first and second selected cell 16 | 17 | + (instancetype)layout; 18 | 19 | + (instancetype)layoutWithAutoSelectRows:(BOOL)autoSelectRows 20 | panToDeselect:(BOOL)panToDeselect 21 | autoSelectCellsBetweenTouches:(BOOL)autoSelectCellsBetweenTouches; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/PodFiles/SECollectionViewFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // SECollectionViewFlowLayout.m 3 | // SECollectionViewFlowLayout 4 | // 5 | // Created by Chris Wendel on 2014/1/30. 6 | // Copyright (c) 2014 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "SECollectionViewFlowLayout.h" 10 | 11 | static NSString * const kSECollectionViewKeyPath = @"collectionView"; 12 | 13 | @interface SECollectionViewFlowLayout () 14 | 15 | @property (nonatomic, assign, getter = isSelecting) BOOL selecting; 16 | 17 | // Pan gesture states 18 | @property (nonatomic, assign) BOOL selectedRow; 19 | @property (nonatomic, assign) BOOL selectRowCancelled; 20 | @property (nonatomic, assign) BOOL pannedFromFirstColumn; 21 | @property (nonatomic, assign) BOOL pannedFromLastColumn; 22 | @property (nonatomic, assign, getter = isDeselecting) BOOL deselecting; 23 | 24 | // Touch gesture states 25 | @property (nonatomic, strong) NSIndexPath *initialSelectedIndexPath; 26 | 27 | @property (nonatomic, strong) NSIndexPath *previousIndexPath; // Used for auto selecting rows 28 | 29 | @end 30 | 31 | @implementation SECollectionViewFlowLayout 32 | 33 | #pragma mark - Initializers 34 | 35 | + (instancetype)layout 36 | { 37 | return [[self alloc] init]; 38 | } 39 | 40 | + (instancetype)layoutWithAutoSelectRows:(BOOL)autoSelectRows 41 | panToDeselect:(BOOL)panToDeselect 42 | autoSelectCellsBetweenTouches:(BOOL)autoSelectCellsBetweenTouches 43 | { 44 | return [[self alloc] initWithAutoSelectRows:autoSelectRows panToDeselect:panToDeselect autoSelectCellsBetweenTouches:autoSelectCellsBetweenTouches]; 45 | } 46 | 47 | - (instancetype)initWithAutoSelectRows:(BOOL)autoSelectRows 48 | panToDeselect:(BOOL)panToDeselect 49 | autoSelectCellsBetweenTouches:(BOOL)autoSelectCellsBetweenTouches 50 | { 51 | self = [super init]; 52 | 53 | if (self) { 54 | _autoSelectRows = autoSelectRows; 55 | _panToDeselect = panToDeselect; 56 | _autoSelectCellsBetweenTouches = autoSelectCellsBetweenTouches; 57 | 58 | [self initializer]; 59 | } 60 | 61 | return self; 62 | } 63 | 64 | - (instancetype)init 65 | { 66 | self = [super init]; 67 | 68 | if (self) { 69 | // All default to NO 70 | _panToDeselect = NO; 71 | _autoSelectRows = NO; 72 | _autoSelectCellsBetweenTouches = NO; 73 | 74 | [self initializer]; 75 | } 76 | 77 | return self; 78 | } 79 | 80 | - (void)initializer 81 | { 82 | // Pan states 83 | _selectRowCancelled = NO; 84 | _selecting = NO; 85 | _selectedRow = NO; 86 | _pannedFromFirstColumn = NO; 87 | _deselecting = NO; 88 | 89 | // Collection view layout properties 90 | self.minimumLineSpacing = 2.0; 91 | self.minimumInteritemSpacing = 2.0; 92 | self.itemSize = CGSizeMake(75.f, 75.f); 93 | 94 | // KVO 95 | [self addObserver:self forKeyPath:kSECollectionViewKeyPath options:NSKeyValueObservingOptionNew context:nil]; 96 | } 97 | 98 | - (void)setupCollectionView 99 | { 100 | // Add tap gesture recognizer 101 | UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; 102 | [self.collectionView addGestureRecognizer:tapGestureRecognizer]; 103 | 104 | // Add pan gesture recognizer 105 | UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; 106 | panGestureRecognizer.delegate = self; 107 | [self.collectionView addGestureRecognizer:panGestureRecognizer]; 108 | } 109 | 110 | #pragma mark - Gesture handling 111 | 112 | - (void)handleTapGesture:(UITapGestureRecognizer *)tapGestureRecognizer 113 | { 114 | // Get index path at tapped point 115 | CGPoint point = [tapGestureRecognizer locationInView:self.collectionView]; 116 | NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point]; 117 | 118 | // Handle tap 119 | if (indexPath) { 120 | UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 121 | if (cell.isSelected) { 122 | [self deselectCellAtIndexPath:indexPath]; 123 | } else { 124 | // Check if we should handle auto selecting the cells between two touches 125 | if (self.autoSelectCellsBetweenTouches) { 126 | if (self.initialSelectedIndexPath) { 127 | [self selectAllItemsFromIndexPath:self.initialSelectedIndexPath toIndexPath:indexPath]; 128 | self.initialSelectedIndexPath = nil; 129 | } else { 130 | self.initialSelectedIndexPath = indexPath; 131 | } 132 | } 133 | 134 | [self selectCellAtIndexPath:indexPath]; 135 | } 136 | } 137 | } 138 | 139 | - (void)handlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer 140 | { 141 | // Get velocity and point of pan 142 | CGPoint velocity = [panGestureRecognizer velocityInView:self.collectionView]; 143 | CGPoint point = [panGestureRecognizer locationInView:self.collectionView]; 144 | 145 | if (!self.collectionView.isDecelerating) { 146 | // Handle pan 147 | if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) { 148 | // Reset pan states 149 | self.selecting = NO; 150 | self.selectedRow = NO; 151 | self.selectRowCancelled = NO; 152 | self.pannedFromFirstColumn = NO; 153 | self.pannedFromLastColumn = NO; 154 | self.deselecting = NO; 155 | self.previousIndexPath = nil; 156 | } else { 157 | if (fabs(velocity.x) < fabs(velocity.y) && !self.selecting) { 158 | // Register as scrolling the collection view 159 | self.selecting = NO; 160 | }else { 161 | // Register as selecting the cells, not scrolling the collection view 162 | self.selecting = YES; 163 | NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point]; 164 | if (indexPath) { 165 | UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 166 | if (cell.selected) { 167 | if (self.panToDeselect) { 168 | if (!self.previousIndexPath && ![self.previousIndexPath isEqual:indexPath]) self.deselecting = YES; 169 | if (self.deselecting) [self deselectCellAtIndexPath:indexPath]; 170 | } 171 | } else { 172 | if (!self.deselecting) { 173 | [self selectCellAtIndexPath:indexPath]; 174 | 175 | if (self.autoSelectRows) [self handleAutoSelectingRowsAtIndexPath:indexPath]; 176 | } 177 | } 178 | 179 | // Update previousIndexPath 180 | self.previousIndexPath = indexPath; 181 | } 182 | } 183 | } 184 | } 185 | } 186 | 187 | #pragma mark - Auto select rows helpers 188 | 189 | - (void)handleAutoSelectingRowsAtIndexPath:(NSIndexPath *)indexPath 190 | { 191 | UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 192 | 193 | NSIndexPath *nextIndexPath = [NSIndexPath indexPathForItem:indexPath.row + 1 inSection:indexPath.section]; 194 | UICollectionViewCell *nextCell = [self.collectionView cellForItemAtIndexPath:nextIndexPath]; 195 | 196 | // Check if this is the initial pan from the first column 197 | if (!self.previousIndexPath) { 198 | if (cell.frame.origin.x == self.minimumInteritemSpacing) { 199 | // Is the first cell in the row 200 | self.pannedFromFirstColumn = YES; 201 | } else if (!nextCell || nextCell.frame.origin.x < cell.frame.origin.x) { 202 | self.pannedFromLastColumn = YES; 203 | } 204 | } 205 | 206 | // Check to make sure we have not panned to another section 207 | if (abs(self.previousIndexPath.section != indexPath.section)) { 208 | self.selectedRow = NO; 209 | } 210 | 211 | // Figure out if this cell is in the first or last column 212 | BOOL didSelectAllItemsInRow = NO; 213 | if (!nextCell || nextCell.frame.origin.x < cell.frame.origin.x){ 214 | if (self.pannedFromFirstColumn) { 215 | didSelectAllItemsInRow = [self didSelectAllItemsInRowWithIndexPath:indexPath]; 216 | } 217 | } else if (cell.frame.origin.x == self.minimumInteritemSpacing) { 218 | if (self.pannedFromLastColumn) { 219 | didSelectAllItemsInRow = [self didSelectAllItemsInRowWithIndexPath:indexPath]; 220 | } 221 | } 222 | 223 | // Check if we should cancel the row select 224 | if (self.previousIndexPath && !didSelectAllItemsInRow && labs(self.previousIndexPath.row - indexPath.row) > 1) { 225 | self.selectRowCancelled = YES; 226 | } 227 | } 228 | 229 | #pragma mark - Selection helpers 230 | 231 | - (void)selectCellAtIndexPath:(NSIndexPath *)indexPath 232 | { 233 | [self.collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone]; 234 | [self.collectionView.delegate collectionView:self.collectionView didSelectItemAtIndexPath:indexPath]; 235 | } 236 | 237 | - (void)deselectCellAtIndexPath:(NSIndexPath *)indexPath 238 | { 239 | [self.collectionView deselectItemAtIndexPath:indexPath animated:YES]; 240 | [self.collectionView.delegate collectionView:self.collectionView didDeselectItemAtIndexPath:indexPath]; 241 | } 242 | 243 | - (void)selectAllItemsFromIndexPath:(NSIndexPath *)initialIndexPath toIndexPath:(NSIndexPath *)finalIndexPath 244 | { 245 | if (initialIndexPath.section == finalIndexPath.section) { 246 | // Check if initial and final index paths should be swapped 247 | if (finalIndexPath.row < initialIndexPath.row) { 248 | // Swap them 249 | NSIndexPath *tempFinalIndex = [NSIndexPath indexPathForItem:finalIndexPath.row inSection:finalIndexPath.section]; 250 | finalIndexPath = initialIndexPath; 251 | initialIndexPath = tempFinalIndex; 252 | } 253 | 254 | // Select cells 255 | NSIndexPath *indexPath = initialIndexPath; 256 | for (NSInteger i = initialIndexPath.row; i < finalIndexPath.row; i++) { 257 | UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 258 | if (!cell.isSelected) { 259 | [self selectCellAtIndexPath:indexPath]; 260 | } 261 | indexPath = [NSIndexPath indexPathForItem:indexPath.row + 1 inSection:indexPath.section]; 262 | } 263 | } 264 | } 265 | 266 | - (BOOL)didSelectAllItemsInRowWithIndexPath:(NSIndexPath *)indexPath 267 | { 268 | if (self.selectedRow) { 269 | // A row has been selected, so select all cells in row 270 | if (!self.selectRowCancelled) { 271 | if (self.pannedFromFirstColumn) 272 | [self selectRowFromLastColumnWithIndexPath:indexPath]; 273 | else 274 | [self selectRowFromFirstColumnWithIndexPath:indexPath]; 275 | 276 | return YES; 277 | } 278 | } else { 279 | self.selectedRow = YES; 280 | } 281 | 282 | return NO; 283 | } 284 | 285 | - (void)selectRowFromFirstColumnWithIndexPath:(NSIndexPath *)indexPath; 286 | { 287 | NSIndexPath *rowIndexPath = indexPath; 288 | UICollectionViewCell *cell; 289 | 290 | // Used for figuring out when we are at the last cell on the column 291 | UICollectionViewCell *nextCell; 292 | NSIndexPath *nextIndexPath; 293 | 294 | // Loop through the cells on this row (from left to right) 295 | do { 296 | rowIndexPath = [NSIndexPath indexPathForItem:rowIndexPath.row + 1 inSection:rowIndexPath.section]; 297 | cell = [self.collectionView cellForItemAtIndexPath:rowIndexPath]; 298 | if (!cell.isSelected) [self selectCellAtIndexPath:rowIndexPath]; 299 | 300 | nextIndexPath = [NSIndexPath indexPathForItem:rowIndexPath.row + 1 inSection:rowIndexPath.section]; 301 | nextCell = [self.collectionView cellForItemAtIndexPath:nextIndexPath]; 302 | } while (nextCell.frame.origin.x > cell.frame.origin.x); 303 | } 304 | 305 | - (void)selectRowFromLastColumnWithIndexPath:(NSIndexPath *)indexPath 306 | { 307 | NSIndexPath *rowIndexPath = indexPath; 308 | UICollectionViewCell *cell; 309 | 310 | // Loop through the cells on this row (from right to left) 311 | do { 312 | rowIndexPath = [NSIndexPath indexPathForItem:rowIndexPath.row - 1 inSection:rowIndexPath.section]; 313 | cell = [self.collectionView cellForItemAtIndexPath:rowIndexPath]; 314 | if (!cell.isSelected) [self selectCellAtIndexPath:rowIndexPath]; 315 | } while (cell.frame.origin.x != self.minimumInteritemSpacing); // TODO 316 | } 317 | 318 | #pragma mark - UIGestureRecognizerDelegate 319 | 320 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 321 | { 322 | return YES; 323 | } 324 | 325 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 326 | { 327 | BOOL recognizeSimultaneously = !self.isSelecting; 328 | return recognizeSimultaneously; 329 | } 330 | 331 | #pragma mark - Key-Value Observing methods 332 | 333 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 334 | { 335 | if ([keyPath isEqualToString:kSECollectionViewKeyPath]) { 336 | if (self.collectionView != nil) { 337 | [self setupCollectionView]; 338 | } 339 | } 340 | } 341 | 342 | #pragma mark - Dealloc 343 | 344 | - (void)dealloc 345 | { 346 | [self removeObserver:self forKeyPath:kSECollectionViewKeyPath]; 347 | } 348 | 349 | @end 350 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/SEAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SEAppDelegate.h 3 | // SECollectionViewFlowLayout 4 | // 5 | // Created by Chris Wendel on 2/17/14. 6 | // Copyright (c) 2014 CEWendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SEAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/SEAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SEAppDelegate.m 3 | // SECollectionViewFlowLayout 4 | // 5 | // Created by Chris Wendel on 2/17/14. 6 | // Copyright (c) 2014 CEWendel. All rights reserved. 7 | // 8 | 9 | #import "SEAppDelegate.h" 10 | 11 | @implementation SEAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/SECollectionViewFlowLayout-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.CEWendel.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/SECollectionViewFlowLayout-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 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/SEViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SEViewController.h 3 | // SECollectionViewFlowLayout 4 | // 5 | // Created by Chris Wendel on 2/17/14. 6 | // Copyright (c) 2014 CEWendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QBImagePickerController.h" 12 | 13 | @interface SEViewController : UITableViewController 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/SEViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SEViewController.m 3 | // SECollectionViewFlowLayout 4 | // 5 | // Created by Chris Wendel on 2/17/14. 6 | // Copyright (c) 2014 CEWendel. All rights reserved. 7 | // 8 | 9 | #import "SEViewController.h" 10 | #import 11 | 12 | @implementation SEViewController 13 | 14 | - (void)viewDidLoad 15 | { 16 | [super viewDidLoad]; 17 | 18 | if (![QBImagePickerController isAccessible]) { 19 | NSLog(@"Error: Source is not accessible."); 20 | } 21 | } 22 | 23 | 24 | #pragma mark - UITableViewDelegate 25 | 26 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 27 | { 28 | QBImagePickerController *imagePickerController = [[QBImagePickerController alloc] init]; 29 | imagePickerController.delegate = self; 30 | imagePickerController.allowsMultipleSelection = (indexPath.section == 1); 31 | 32 | if (indexPath.section == 1) { 33 | switch (indexPath.row) { 34 | case 1: 35 | imagePickerController.minimumNumberOfSelection = 3; 36 | break; 37 | 38 | case 2: 39 | imagePickerController.maximumNumberOfSelection = 6; 40 | break; 41 | 42 | case 3: 43 | imagePickerController.minimumNumberOfSelection = 3; 44 | imagePickerController.maximumNumberOfSelection = 6; 45 | break; 46 | 47 | default: 48 | break; 49 | } 50 | } 51 | 52 | if (indexPath.section == 0 && indexPath.row == 1) { 53 | [self.navigationController pushViewController:imagePickerController animated:YES]; 54 | } else { 55 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:imagePickerController]; 56 | [self presentViewController:navigationController animated:YES completion:NULL]; 57 | } 58 | } 59 | 60 | - (void)dismissImagePickerController 61 | { 62 | if (self.presentedViewController) { 63 | [self dismissViewControllerAnimated:YES completion:NULL]; 64 | } else { 65 | [self.navigationController popToViewController:self animated:YES]; 66 | } 67 | } 68 | 69 | 70 | #pragma mark - QBImagePickerControllerDelegate 71 | 72 | - (void)imagePickerController:(QBImagePickerController *)imagePickerController didSelectAsset:(ALAsset *)asset 73 | { 74 | NSLog(@"*** imagePickerController:didSelectAsset:"); 75 | NSLog(@"%@", asset); 76 | 77 | [self dismissImagePickerController]; 78 | } 79 | 80 | - (void)imagePickerController:(QBImagePickerController *)imagePickerController didSelectAssets:(NSArray *)assets 81 | { 82 | NSLog(@"*** imagePickerController:didSelectAssets:"); 83 | NSLog(@"%@", assets); 84 | 85 | [self dismissImagePickerController]; 86 | } 87 | 88 | - (void)imagePickerControllerDidCancel:(QBImagePickerController *)imagePickerController 89 | { 90 | NSLog(@"*** imagePickerControllerDidCancel:"); 91 | 92 | [self dismissImagePickerController]; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /SECollectionViewFlowLayout/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SECollectionViewFlowLayout 4 | // 5 | // Created by Chris Wendel on 2/17/14. 6 | // Copyright (c) 2014 CEWendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "SEAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([SEAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SEQBImagePickerController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SEQBImagePickerController' 3 | s.version = '0.0.1' 4 | s.author = { 'Chris Wendel' => 'chriwend@umich.edu' } 5 | s.homepage = 'https://github.com/CEWendel/SECollectionViewFlowLayout' 6 | s.summary = 'A clone of UIImagePickerController with multiple selection support that supports swipe-to-select' 7 | s.license = 'MIT' 8 | s.source = { :git => 'https://github.com/CEWendel/SECollectionViewFlowLayout.git', :tag => '0.0.1' } 9 | s.source_files = 'SECollectionViewFlowLayout/PodFiles/*.{h,m}' 10 | s.platform = :ios 11 | s.ios.deployment_target = '6.0' 12 | s.requires_arc = true 13 | end 14 | --------------------------------------------------------------------------------