├── .gitignore ├── FTImagePickerController.podspec ├── FTImagePickerController.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── leo.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── leo.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── FTImagePickerController.xcscheme │ └── xcschememanagement.plist ├── FTImagePickerController ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── FTImagePickerController.bundle │ ├── emptyFolder@2x.png │ ├── photo_rule@2x.png │ ├── tickH@2x.png │ ├── tickH@3x.png │ ├── tickw@2x.png │ └── tickw@3x.png ├── FTImagePickerController │ ├── FTAlbumsViewCell.h │ ├── FTAlbumsViewCell.m │ ├── FTAlbumsViewController.h │ ├── FTAlbumsViewController.m │ ├── FTAssetsImageManager.h │ ├── FTAssetsImageManager.m │ ├── FTBadgeView.h │ ├── FTBadgeView.m │ ├── FTBrowserViewCell.h │ ├── FTBrowserViewCell.m │ ├── FTBrowserViewController.h │ ├── FTBrowserViewController.m │ ├── FTGridViewCell.h │ ├── FTGridViewCell.m │ ├── FTGridViewController.h │ ├── FTGridViewController.m │ ├── FTImageClipViewController.h │ ├── FTImageClipViewController.m │ ├── FTImagePickerController.h │ ├── FTImagePickerController.m │ ├── UIImage+FTHelper.h │ └── UIImage+FTHelper.m ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.xcuserstate 3 | -------------------------------------------------------------------------------- /FTImagePickerController.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint SCImagePickerController.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "FTImagePickerController" 19 | s.version = "1.0.0" 20 | s.summary = "A layout similar to UICollectionViewFlowLayout." 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | s.description = <<-DESC 28 | A layout similar to UICollectionViewFlowLayout for Photo. 29 | DESC 30 | 31 | s.homepage = "https://github.com/alotofleo2/FTImagePickerController" 32 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 33 | 34 | 35 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 36 | # 37 | # Licensing your code is important. See http://choosealicense.com for more info. 38 | # CocoaPods will detect a license file if there is a named LICENSE* 39 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 40 | # 41 | 42 | s.license = "MIT" 43 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 44 | 45 | 46 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 47 | # 48 | # Specify the authors of the library, with email addresses. Email addresses 49 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 50 | # accepts just a name if you'd rather not provide an email address. 51 | # 52 | # Specify a social_media_url where others can refer to, for example a twitter 53 | # profile URL. 54 | # 55 | 56 | s.author = { "fangtao112" => "alotofleo2@126.com" } 57 | # Or just: s.author = "fangtao112" 58 | # s.authors = { "fangtao112" => "alotofleo2@126.com" } 59 | # s.social_media_url = "http://twitter.com/fangtao" 60 | 61 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 62 | # 63 | # If this Pod runs only on iOS or OS X, then specify the platform and 64 | # the deployment target. You can optionally include the target after the platform. 65 | # 66 | 67 | # s.platform = :ios 68 | s.platform = :ios, "7.0" 69 | 70 | # When using multiple platforms 71 | # s.ios.deployment_target = "5.0" 72 | # s.osx.deployment_target = "10.7" 73 | # s.watchos.deployment_target = "2.0" 74 | # s.tvos.deployment_target = "9.0" 75 | 76 | 77 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 78 | # 79 | # Specify the location from where the source should be retrieved. 80 | # Supports git, hg, bzr, svn and HTTP. 81 | # 82 | 83 | s.source = { :git => "https://github.com/alotofleo2/FTImagePickerController.git", :tag => s.version.to_s } 84 | 85 | 86 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 87 | # 88 | # CocoaPods is smart about how it includes source code. For source files 89 | # giving a folder will include any swift, h, m, mm, c & cpp files. 90 | # For header files it will include any header in the folder. 91 | # Not including the public_header_files will make all headers public. 92 | # 93 | 94 | s.source_files = 'FTImagePickerController/FTImagePickerController/*.{h,m}' 95 | s.resource_bundles = { 96 | 'FTImagePickerController' => ['FTImagePickerController/FTImagePickerController.bundle/*.png'] 97 | } 98 | 99 | # s.public_header_files = "Classes/**/*.h" 100 | 101 | 102 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 103 | # 104 | # A list of resources included with the Pod. These are copied into the 105 | # target bundle with a build phase script. Anything else will be cleaned. 106 | # You can preserve files from being cleaned, please don't preserve 107 | # non-essential files like tests, examples and documentation. 108 | # 109 | 110 | # s.resource = "icon.png" 111 | # s.resources = "Resources.bundle/*.png" 112 | 113 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 114 | 115 | 116 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 117 | # 118 | # Link your library with frameworks, or libraries. Libraries do not include 119 | # the lib prefix of their name. 120 | # 121 | 122 | # s.framework = "SomeFramework" 123 | # s.frameworks = "SomeFramework", "AnotherFramework" 124 | 125 | # s.library = "iconv" 126 | # s.libraries = "iconv", "xml2" 127 | 128 | 129 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 130 | # 131 | # If your library depends on compiler flags you can set them in the xcconfig hash 132 | # where they will only apply to your library. If you depend on other Podspecs 133 | # you can include multiple dependencies to ensure it works. 134 | 135 | s.requires_arc = true 136 | 137 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 138 | # s.dependency "JSONKit", "~> 1.4" 139 | 140 | end 141 | -------------------------------------------------------------------------------- /FTImagePickerController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B30CF2A51CFA6C5600755DA8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B30CF2A41CFA6C5600755DA8 /* main.m */; }; 11 | B30CF2A81CFA6C5600755DA8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B30CF2A71CFA6C5600755DA8 /* AppDelegate.m */; }; 12 | B30CF2AB1CFA6C5600755DA8 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B30CF2AA1CFA6C5600755DA8 /* ViewController.m */; }; 13 | B30CF2AE1CFA6C5600755DA8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B30CF2AC1CFA6C5600755DA8 /* Main.storyboard */; }; 14 | B30CF2B31CFA6C5600755DA8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B30CF2B11CFA6C5600755DA8 /* LaunchScreen.storyboard */; }; 15 | B38018051D31628B00AA0EB9 /* FTImagePickerController.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B38018041D31628B00AA0EB9 /* FTImagePickerController.bundle */; }; 16 | B3CFD5C61D32401C0051ED78 /* FTAlbumsViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5B11D32401C0051ED78 /* FTAlbumsViewCell.m */; }; 17 | B3CFD5C71D32401C0051ED78 /* FTAlbumsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5B31D32401C0051ED78 /* FTAlbumsViewController.m */; }; 18 | B3CFD5C81D32401C0051ED78 /* FTAssetsImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5B51D32401C0051ED78 /* FTAssetsImageManager.m */; }; 19 | B3CFD5C91D32401C0051ED78 /* FTBadgeView.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5B71D32401C0051ED78 /* FTBadgeView.m */; }; 20 | B3CFD5CA1D32401C0051ED78 /* FTBrowserViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5B91D32401C0051ED78 /* FTBrowserViewCell.m */; }; 21 | B3CFD5CB1D32401C0051ED78 /* FTBrowserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5BB1D32401C0051ED78 /* FTBrowserViewController.m */; }; 22 | B3CFD5CC1D32401C0051ED78 /* FTGridViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5BD1D32401C0051ED78 /* FTGridViewCell.m */; }; 23 | B3CFD5CD1D32401C0051ED78 /* FTGridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5BF1D32401C0051ED78 /* FTGridViewController.m */; }; 24 | B3CFD5CE1D32401C0051ED78 /* FTImageClipViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5C11D32401C0051ED78 /* FTImageClipViewController.m */; }; 25 | B3CFD5CF1D32401C0051ED78 /* FTImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5C31D32401C0051ED78 /* FTImagePickerController.m */; }; 26 | B3CFD5D01D32401C0051ED78 /* UIImage+FTHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = B3CFD5C51D32401C0051ED78 /* UIImage+FTHelper.m */; }; 27 | B3FA67ED1CFBE16F0093C4B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B3FA67EC1CFBE16F0093C4B9 /* Assets.xcassets */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | B30CF2A01CFA6C5600755DA8 /* FTImagePickerController.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FTImagePickerController.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | B30CF2A41CFA6C5600755DA8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | B30CF2A61CFA6C5600755DA8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 34 | B30CF2A71CFA6C5600755DA8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 35 | B30CF2A91CFA6C5600755DA8 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 36 | B30CF2AA1CFA6C5600755DA8 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 37 | B30CF2AD1CFA6C5600755DA8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | B30CF2B21CFA6C5600755DA8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 39 | B30CF2B41CFA6C5600755DA8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | B38018041D31628B00AA0EB9 /* FTImagePickerController.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = FTImagePickerController.bundle; sourceTree = ""; }; 41 | B3CFD5B01D32401C0051ED78 /* FTAlbumsViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTAlbumsViewCell.h; sourceTree = ""; }; 42 | B3CFD5B11D32401C0051ED78 /* FTAlbumsViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTAlbumsViewCell.m; sourceTree = ""; }; 43 | B3CFD5B21D32401C0051ED78 /* FTAlbumsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTAlbumsViewController.h; sourceTree = ""; }; 44 | B3CFD5B31D32401C0051ED78 /* FTAlbumsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTAlbumsViewController.m; sourceTree = ""; }; 45 | B3CFD5B41D32401C0051ED78 /* FTAssetsImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTAssetsImageManager.h; sourceTree = ""; }; 46 | B3CFD5B51D32401C0051ED78 /* FTAssetsImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTAssetsImageManager.m; sourceTree = ""; }; 47 | B3CFD5B61D32401C0051ED78 /* FTBadgeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTBadgeView.h; sourceTree = ""; }; 48 | B3CFD5B71D32401C0051ED78 /* FTBadgeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTBadgeView.m; sourceTree = ""; }; 49 | B3CFD5B81D32401C0051ED78 /* FTBrowserViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTBrowserViewCell.h; sourceTree = ""; }; 50 | B3CFD5B91D32401C0051ED78 /* FTBrowserViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTBrowserViewCell.m; sourceTree = ""; }; 51 | B3CFD5BA1D32401C0051ED78 /* FTBrowserViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTBrowserViewController.h; sourceTree = ""; }; 52 | B3CFD5BB1D32401C0051ED78 /* FTBrowserViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTBrowserViewController.m; sourceTree = ""; }; 53 | B3CFD5BC1D32401C0051ED78 /* FTGridViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTGridViewCell.h; sourceTree = ""; }; 54 | B3CFD5BD1D32401C0051ED78 /* FTGridViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTGridViewCell.m; sourceTree = ""; }; 55 | B3CFD5BE1D32401C0051ED78 /* FTGridViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTGridViewController.h; sourceTree = ""; }; 56 | B3CFD5BF1D32401C0051ED78 /* FTGridViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTGridViewController.m; sourceTree = ""; }; 57 | B3CFD5C01D32401C0051ED78 /* FTImageClipViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTImageClipViewController.h; sourceTree = ""; }; 58 | B3CFD5C11D32401C0051ED78 /* FTImageClipViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTImageClipViewController.m; sourceTree = ""; }; 59 | B3CFD5C21D32401C0051ED78 /* FTImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FTImagePickerController.h; sourceTree = ""; }; 60 | B3CFD5C31D32401C0051ED78 /* FTImagePickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FTImagePickerController.m; sourceTree = ""; }; 61 | B3CFD5C41D32401C0051ED78 /* UIImage+FTHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+FTHelper.h"; sourceTree = ""; }; 62 | B3CFD5C51D32401C0051ED78 /* UIImage+FTHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+FTHelper.m"; sourceTree = ""; }; 63 | B3FA67EC1CFBE16F0093C4B9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | B30CF29D1CFA6C5600755DA8 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | B30CF2971CFA6C5600755DA8 = { 78 | isa = PBXGroup; 79 | children = ( 80 | B30CF2A21CFA6C5600755DA8 /* FTImagePickerController */, 81 | B30CF2A11CFA6C5600755DA8 /* Products */, 82 | ); 83 | sourceTree = ""; 84 | }; 85 | B30CF2A11CFA6C5600755DA8 /* Products */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | B30CF2A01CFA6C5600755DA8 /* FTImagePickerController.app */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | B30CF2A21CFA6C5600755DA8 /* FTImagePickerController */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | B3CFD5AF1D32401C0051ED78 /* FTImagePickerController */, 97 | B38018041D31628B00AA0EB9 /* FTImagePickerController.bundle */, 98 | B30CF2A61CFA6C5600755DA8 /* AppDelegate.h */, 99 | B30CF2A71CFA6C5600755DA8 /* AppDelegate.m */, 100 | B30CF2A91CFA6C5600755DA8 /* ViewController.h */, 101 | B30CF2AA1CFA6C5600755DA8 /* ViewController.m */, 102 | B30CF2AC1CFA6C5600755DA8 /* Main.storyboard */, 103 | B3FA67EC1CFBE16F0093C4B9 /* Assets.xcassets */, 104 | B30CF2B11CFA6C5600755DA8 /* LaunchScreen.storyboard */, 105 | B30CF2B41CFA6C5600755DA8 /* Info.plist */, 106 | B30CF2A31CFA6C5600755DA8 /* Supporting Files */, 107 | ); 108 | path = FTImagePickerController; 109 | sourceTree = ""; 110 | }; 111 | B30CF2A31CFA6C5600755DA8 /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | B30CF2A41CFA6C5600755DA8 /* main.m */, 115 | ); 116 | name = "Supporting Files"; 117 | sourceTree = ""; 118 | }; 119 | B3CFD5AF1D32401C0051ED78 /* FTImagePickerController */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | B3CFD5B01D32401C0051ED78 /* FTAlbumsViewCell.h */, 123 | B3CFD5B11D32401C0051ED78 /* FTAlbumsViewCell.m */, 124 | B3CFD5B21D32401C0051ED78 /* FTAlbumsViewController.h */, 125 | B3CFD5B31D32401C0051ED78 /* FTAlbumsViewController.m */, 126 | B3CFD5B41D32401C0051ED78 /* FTAssetsImageManager.h */, 127 | B3CFD5B51D32401C0051ED78 /* FTAssetsImageManager.m */, 128 | B3CFD5B61D32401C0051ED78 /* FTBadgeView.h */, 129 | B3CFD5B71D32401C0051ED78 /* FTBadgeView.m */, 130 | B3CFD5B81D32401C0051ED78 /* FTBrowserViewCell.h */, 131 | B3CFD5B91D32401C0051ED78 /* FTBrowserViewCell.m */, 132 | B3CFD5BA1D32401C0051ED78 /* FTBrowserViewController.h */, 133 | B3CFD5BB1D32401C0051ED78 /* FTBrowserViewController.m */, 134 | B3CFD5BC1D32401C0051ED78 /* FTGridViewCell.h */, 135 | B3CFD5BD1D32401C0051ED78 /* FTGridViewCell.m */, 136 | B3CFD5BE1D32401C0051ED78 /* FTGridViewController.h */, 137 | B3CFD5BF1D32401C0051ED78 /* FTGridViewController.m */, 138 | B3CFD5C01D32401C0051ED78 /* FTImageClipViewController.h */, 139 | B3CFD5C11D32401C0051ED78 /* FTImageClipViewController.m */, 140 | B3CFD5C21D32401C0051ED78 /* FTImagePickerController.h */, 141 | B3CFD5C31D32401C0051ED78 /* FTImagePickerController.m */, 142 | B3CFD5C41D32401C0051ED78 /* UIImage+FTHelper.h */, 143 | B3CFD5C51D32401C0051ED78 /* UIImage+FTHelper.m */, 144 | ); 145 | path = FTImagePickerController; 146 | sourceTree = ""; 147 | }; 148 | /* End PBXGroup section */ 149 | 150 | /* Begin PBXNativeTarget section */ 151 | B30CF29F1CFA6C5600755DA8 /* FTImagePickerController */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = B30CF2B71CFA6C5600755DA8 /* Build configuration list for PBXNativeTarget "FTImagePickerController" */; 154 | buildPhases = ( 155 | B30CF29C1CFA6C5600755DA8 /* Sources */, 156 | B30CF29D1CFA6C5600755DA8 /* Frameworks */, 157 | B30CF29E1CFA6C5600755DA8 /* Resources */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = FTImagePickerController; 164 | productName = FTImagePickerController; 165 | productReference = B30CF2A01CFA6C5600755DA8 /* FTImagePickerController.app */; 166 | productType = "com.apple.product-type.application"; 167 | }; 168 | /* End PBXNativeTarget section */ 169 | 170 | /* Begin PBXProject section */ 171 | B30CF2981CFA6C5600755DA8 /* Project object */ = { 172 | isa = PBXProject; 173 | attributes = { 174 | LastUpgradeCheck = 0730; 175 | ORGANIZATIONNAME = taofang; 176 | TargetAttributes = { 177 | B30CF29F1CFA6C5600755DA8 = { 178 | CreatedOnToolsVersion = 7.3; 179 | DevelopmentTeam = W6P99MFM4M; 180 | }; 181 | }; 182 | }; 183 | buildConfigurationList = B30CF29B1CFA6C5600755DA8 /* Build configuration list for PBXProject "FTImagePickerController" */; 184 | compatibilityVersion = "Xcode 3.2"; 185 | developmentRegion = English; 186 | hasScannedForEncodings = 0; 187 | knownRegions = ( 188 | en, 189 | Base, 190 | ); 191 | mainGroup = B30CF2971CFA6C5600755DA8; 192 | productRefGroup = B30CF2A11CFA6C5600755DA8 /* Products */; 193 | projectDirPath = ""; 194 | projectRoot = ""; 195 | targets = ( 196 | B30CF29F1CFA6C5600755DA8 /* FTImagePickerController */, 197 | ); 198 | }; 199 | /* End PBXProject section */ 200 | 201 | /* Begin PBXResourcesBuildPhase section */ 202 | B30CF29E1CFA6C5600755DA8 /* Resources */ = { 203 | isa = PBXResourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | B30CF2B31CFA6C5600755DA8 /* LaunchScreen.storyboard in Resources */, 207 | B38018051D31628B00AA0EB9 /* FTImagePickerController.bundle in Resources */, 208 | B3FA67ED1CFBE16F0093C4B9 /* Assets.xcassets in Resources */, 209 | B30CF2AE1CFA6C5600755DA8 /* Main.storyboard in Resources */, 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXResourcesBuildPhase section */ 214 | 215 | /* Begin PBXSourcesBuildPhase section */ 216 | B30CF29C1CFA6C5600755DA8 /* Sources */ = { 217 | isa = PBXSourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | B3CFD5C81D32401C0051ED78 /* FTAssetsImageManager.m in Sources */, 221 | B3CFD5CF1D32401C0051ED78 /* FTImagePickerController.m in Sources */, 222 | B3CFD5C91D32401C0051ED78 /* FTBadgeView.m in Sources */, 223 | B30CF2AB1CFA6C5600755DA8 /* ViewController.m in Sources */, 224 | B3CFD5CD1D32401C0051ED78 /* FTGridViewController.m in Sources */, 225 | B30CF2A81CFA6C5600755DA8 /* AppDelegate.m in Sources */, 226 | B3CFD5CC1D32401C0051ED78 /* FTGridViewCell.m in Sources */, 227 | B3CFD5CE1D32401C0051ED78 /* FTImageClipViewController.m in Sources */, 228 | B3CFD5C61D32401C0051ED78 /* FTAlbumsViewCell.m in Sources */, 229 | B3CFD5C71D32401C0051ED78 /* FTAlbumsViewController.m in Sources */, 230 | B30CF2A51CFA6C5600755DA8 /* main.m in Sources */, 231 | B3CFD5CA1D32401C0051ED78 /* FTBrowserViewCell.m in Sources */, 232 | B3CFD5CB1D32401C0051ED78 /* FTBrowserViewController.m in Sources */, 233 | B3CFD5D01D32401C0051ED78 /* UIImage+FTHelper.m in Sources */, 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | /* End PBXSourcesBuildPhase section */ 238 | 239 | /* Begin PBXVariantGroup section */ 240 | B30CF2AC1CFA6C5600755DA8 /* Main.storyboard */ = { 241 | isa = PBXVariantGroup; 242 | children = ( 243 | B30CF2AD1CFA6C5600755DA8 /* Base */, 244 | ); 245 | name = Main.storyboard; 246 | sourceTree = ""; 247 | }; 248 | B30CF2B11CFA6C5600755DA8 /* LaunchScreen.storyboard */ = { 249 | isa = PBXVariantGroup; 250 | children = ( 251 | B30CF2B21CFA6C5600755DA8 /* Base */, 252 | ); 253 | name = LaunchScreen.storyboard; 254 | sourceTree = ""; 255 | }; 256 | /* End PBXVariantGroup section */ 257 | 258 | /* Begin XCBuildConfiguration section */ 259 | B30CF2B51CFA6C5600755DA8 /* Debug */ = { 260 | isa = XCBuildConfiguration; 261 | buildSettings = { 262 | ALWAYS_SEARCH_USER_PATHS = NO; 263 | CLANG_ANALYZER_NONNULL = YES; 264 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 265 | CLANG_CXX_LIBRARY = "libc++"; 266 | CLANG_ENABLE_MODULES = YES; 267 | CLANG_ENABLE_OBJC_ARC = YES; 268 | CLANG_WARN_BOOL_CONVERSION = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 275 | CLANG_WARN_UNREACHABLE_CODE = YES; 276 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 277 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 278 | COPY_PHASE_STRIP = NO; 279 | DEBUG_INFORMATION_FORMAT = dwarf; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | ENABLE_TESTABILITY = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu99; 283 | GCC_DYNAMIC_NO_PIC = NO; 284 | GCC_NO_COMMON_BLOCKS = YES; 285 | GCC_OPTIMIZATION_LEVEL = 0; 286 | GCC_PREPROCESSOR_DEFINITIONS = ( 287 | "DEBUG=1", 288 | "$(inherited)", 289 | ); 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 297 | MTL_ENABLE_DEBUG_INFO = YES; 298 | ONLY_ACTIVE_ARCH = YES; 299 | SDKROOT = iphoneos; 300 | TARGETED_DEVICE_FAMILY = "1,2"; 301 | }; 302 | name = Debug; 303 | }; 304 | B30CF2B61CFA6C5600755DA8 /* Release */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ALWAYS_SEARCH_USER_PATHS = NO; 308 | CLANG_ANALYZER_NONNULL = YES; 309 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 310 | CLANG_CXX_LIBRARY = "libc++"; 311 | CLANG_ENABLE_MODULES = YES; 312 | CLANG_ENABLE_OBJC_ARC = YES; 313 | CLANG_WARN_BOOL_CONVERSION = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 320 | CLANG_WARN_UNREACHABLE_CODE = YES; 321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 322 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 323 | COPY_PHASE_STRIP = NO; 324 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 325 | ENABLE_NS_ASSERTIONS = NO; 326 | ENABLE_STRICT_OBJC_MSGSEND = YES; 327 | GCC_C_LANGUAGE_STANDARD = gnu99; 328 | GCC_NO_COMMON_BLOCKS = YES; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 336 | MTL_ENABLE_DEBUG_INFO = NO; 337 | SDKROOT = iphoneos; 338 | TARGETED_DEVICE_FAMILY = "1,2"; 339 | VALIDATE_PRODUCT = YES; 340 | }; 341 | name = Release; 342 | }; 343 | B30CF2B81CFA6C5600755DA8 /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 347 | INFOPLIST_FILE = FTImagePickerController/Info.plist; 348 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 349 | PRODUCT_BUNDLE_IDENTIFIER = com.meilishuo.FTImagePickerController; 350 | PRODUCT_NAME = "$(TARGET_NAME)"; 351 | }; 352 | name = Debug; 353 | }; 354 | B30CF2B91CFA6C5600755DA8 /* Release */ = { 355 | isa = XCBuildConfiguration; 356 | buildSettings = { 357 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 358 | INFOPLIST_FILE = FTImagePickerController/Info.plist; 359 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 360 | PRODUCT_BUNDLE_IDENTIFIER = com.meilishuo.FTImagePickerController; 361 | PRODUCT_NAME = "$(TARGET_NAME)"; 362 | }; 363 | name = Release; 364 | }; 365 | /* End XCBuildConfiguration section */ 366 | 367 | /* Begin XCConfigurationList section */ 368 | B30CF29B1CFA6C5600755DA8 /* Build configuration list for PBXProject "FTImagePickerController" */ = { 369 | isa = XCConfigurationList; 370 | buildConfigurations = ( 371 | B30CF2B51CFA6C5600755DA8 /* Debug */, 372 | B30CF2B61CFA6C5600755DA8 /* Release */, 373 | ); 374 | defaultConfigurationIsVisible = 0; 375 | defaultConfigurationName = Release; 376 | }; 377 | B30CF2B71CFA6C5600755DA8 /* Build configuration list for PBXNativeTarget "FTImagePickerController" */ = { 378 | isa = XCConfigurationList; 379 | buildConfigurations = ( 380 | B30CF2B81CFA6C5600755DA8 /* Debug */, 381 | B30CF2B91CFA6C5600755DA8 /* Release */, 382 | ); 383 | defaultConfigurationIsVisible = 0; 384 | defaultConfigurationName = Release; 385 | }; 386 | /* End XCConfigurationList section */ 387 | }; 388 | rootObject = B30CF2981CFA6C5600755DA8 /* Project object */; 389 | } 390 | -------------------------------------------------------------------------------- /FTImagePickerController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FTImagePickerController.xcodeproj/project.xcworkspace/xcuserdata/leo.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alotofleo2/FTImagePickerController/2060ed1aa009d4a96a6875f7819b589f06bd1fae/FTImagePickerController.xcodeproj/project.xcworkspace/xcuserdata/leo.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FTImagePickerController.xcodeproj/xcuserdata/leo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FTImagePickerController.xcodeproj/xcuserdata/leo.xcuserdatad/xcschemes/FTImagePickerController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /FTImagePickerController.xcodeproj/xcuserdata/leo.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FTImagePickerController.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | B30CF29F1CFA6C5600755DA8 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /FTImagePickerController/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /FTImagePickerController/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 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 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /FTImagePickerController/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /FTImagePickerController/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FTImagePickerController/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /FTImagePickerController/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController.bundle/emptyFolder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alotofleo2/FTImagePickerController/2060ed1aa009d4a96a6875f7819b589f06bd1fae/FTImagePickerController/FTImagePickerController.bundle/emptyFolder@2x.png -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController.bundle/photo_rule@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alotofleo2/FTImagePickerController/2060ed1aa009d4a96a6875f7819b589f06bd1fae/FTImagePickerController/FTImagePickerController.bundle/photo_rule@2x.png -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController.bundle/tickH@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alotofleo2/FTImagePickerController/2060ed1aa009d4a96a6875f7819b589f06bd1fae/FTImagePickerController/FTImagePickerController.bundle/tickH@2x.png -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController.bundle/tickH@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alotofleo2/FTImagePickerController/2060ed1aa009d4a96a6875f7819b589f06bd1fae/FTImagePickerController/FTImagePickerController.bundle/tickH@3x.png -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController.bundle/tickw@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alotofleo2/FTImagePickerController/2060ed1aa009d4a96a6875f7819b589f06bd1fae/FTImagePickerController/FTImagePickerController.bundle/tickw@2x.png -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController.bundle/tickw@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alotofleo2/FTImagePickerController/2060ed1aa009d4a96a6875f7819b589f06bd1fae/FTImagePickerController/FTImagePickerController.bundle/tickw@3x.png -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTAlbumsViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTAlbumsViewCell.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | static CGSize const kAlbumThumbnailSize = {57.0, 57.0}; 12 | 13 | @interface FTAlbumsViewCell : UITableViewCell 14 | 15 | @property (nonatomic, strong) UIImageView *thumbnailView; 16 | @property (nonatomic, copy) NSString *representedAlbumIdentifier; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTAlbumsViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTAlbumsViewCell.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTAlbumsViewCell.h" 10 | 11 | @implementation FTAlbumsViewCell 12 | - (void)prepareForReuse { 13 | [super prepareForReuse]; 14 | self.thumbnailView.image = nil; 15 | } 16 | 17 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 18 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 19 | self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 20 | 21 | // thumbnailView 22 | _thumbnailView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, kAlbumThumbnailSize.width, kAlbumThumbnailSize.height)]; 23 | _thumbnailView.contentMode = UIViewContentModeScaleAspectFill; 24 | _thumbnailView.clipsToBounds = YES; 25 | [self.contentView addSubview:_thumbnailView]; 26 | 27 | self.textLabel.translatesAutoresizingMaskIntoConstraints = NO; 28 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[imageView]-(offset)-[textLabel]-|" 29 | options:0 30 | metrics:@{@"offset": @(5)} 31 | views:@{@"textLabel": self.textLabel, 32 | @"imageView": self.thumbnailView}]]; 33 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[textLabel]-|" 34 | options:0 35 | metrics:nil 36 | views:@{@"textLabel": self.textLabel}]]; 37 | } 38 | return self; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTAlbumsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTAlbumsViewController.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FTAlbumsViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTAlbumsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTAlbumsViewController.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTAlbumsViewController.h" 10 | #import "FTImagePickerController.h" 11 | #import "FTGridViewController.h" 12 | #import "FTAlbumsViewCell.h" 13 | #import "FTBadgeView.h" 14 | 15 | static NSString * const FTAlbumsViewCellReuseIdentifier = @"FTAlbumsViewCellReuseIdentifier"; 16 | 17 | @interface FTAlbumsViewController () 18 | 19 | @property (nonatomic, strong) NSArray *fetchResults; 20 | @property (nonatomic, strong) NSArray *assetCollections; 21 | @property (nonatomic, weak) FTImagePickerController *picker; 22 | @property (nonatomic, strong) PHCachingImageManager *imageManager; 23 | @property (nonatomic, strong) FTBadgeView *badgeView; 24 | 25 | @end 26 | 27 | @implementation FTAlbumsViewController 28 | - (instancetype)init { 29 | if (self = [super initWithStyle:UITableViewStylePlain]) { 30 | self.title = @"相册"; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)viewDidLoad { 36 | [super viewDidLoad]; 37 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"取消" 38 | style:UIBarButtonItemStylePlain 39 | target:self.picker 40 | action:@selector(dismiss:)]; 41 | self.tableView.rowHeight = kAlbumThumbnailSize.height + 0.5; 42 | 43 | PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; 44 | if (status == PHAuthorizationStatusNotDetermined) { 45 | [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { 46 | dispatch_async(dispatch_get_main_queue(), ^{ 47 | if (status == PHAuthorizationStatusAuthorized) { 48 | [self showAlbums]; 49 | [self.tableView reloadData]; 50 | } else { 51 | [self showNoAuthority]; 52 | } 53 | }); 54 | }]; 55 | } else if (status == PHAuthorizationStatusAuthorized) { 56 | [self showAlbums]; 57 | } else { 58 | [self showNoAuthority]; 59 | } 60 | } 61 | 62 | // 无权限 63 | - (void)showNoAuthority { 64 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 150)]; 65 | label.textColor = [UIColor darkTextColor]; 66 | label.textAlignment = NSTextAlignmentCenter; 67 | label.font = [UIFont systemFontOfSize:16.0]; 68 | label.text = @"请在\"设置\"->\"隐私\"->\"相册\"开启访问权限"; 69 | self.tableView.tableHeaderView = label; 70 | self.tableView.tableFooterView = [UIView new]; 71 | self.tableView.bounces = NO; 72 | } 73 | 74 | // 有权限 75 | - (void)showAlbums { 76 | self.imageManager = [[PHCachingImageManager alloc] init]; 77 | PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil]; 78 | PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil]; 79 | self.fetchResults = @[smartAlbums, topLevelUserCollections]; 80 | 81 | if (self.picker.allowsMultipleSelection) { 82 | [self attachRightBarButton]; 83 | } 84 | 85 | if (self.picker.sourceType == FTImagePickerControllerSourceTypeSavedPhotosAlbum) { 86 | [self pushCameraRollViewController]; 87 | } 88 | 89 | [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; 90 | } 91 | 92 | - (void)dealloc { 93 | 94 | [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; 95 | 96 | } 97 | 98 | - (void)attachRightBarButton { 99 | UIBarButtonItem *doneButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"完成" 100 | style:UIBarButtonItemStyleDone 101 | target:self.picker 102 | action:@selector(finishPickingAssets:)]; 103 | doneButtonItem.enabled = self.picker.selectedAssets.count > 0; 104 | 105 | self.badgeView = [[FTBadgeView alloc] init]; 106 | self.badgeView.number = self.picker.selectedAssets.count; 107 | UIBarButtonItem *badgeButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.badgeView]; 108 | 109 | self.navigationItem.rightBarButtonItems = @[doneButtonItem, badgeButtonItem]; 110 | } 111 | 112 | - (void)pushCameraRollViewController { 113 | PHFetchResult *collections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil]; 114 | 115 | if (collections.count > 0) { 116 | PHAssetCollection *collection = collections[0]; 117 | 118 | FTGridViewController *cameraRollViewController = [[FTGridViewController alloc] initWithPicker:[self picker]]; 119 | cameraRollViewController.assets = [self assetsInAssetCollection:collection]; 120 | cameraRollViewController.title = collection.localizedTitle; 121 | 122 | [self.navigationController pushViewController:cameraRollViewController animated:NO]; 123 | } 124 | } 125 | 126 | - (FTImagePickerController *)picker { 127 | return (FTImagePickerController *)self.navigationController.parentViewController; 128 | } 129 | 130 | - (void)setFetchResults:(NSArray *)fetchResults { 131 | _fetchResults = fetchResults; 132 | 133 | NSMutableArray *assetCollections = [NSMutableArray array]; 134 | 135 | for (PHFetchResult *fetchResult in fetchResults) 136 | { 137 | for (PHCollection *collection in fetchResult) { 138 | if ([collection isKindOfClass:[PHAssetCollection class]]) 139 | { 140 | PHAssetCollection *assetCollection = (PHAssetCollection *)collection; 141 | PHFetchResult *assets = [self assetsInAssetCollection:assetCollection]; 142 | if (assets.count > 0) { 143 | if (assetCollection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumUserLibrary) { 144 | [assetCollections insertObject:assetCollection atIndex:0]; 145 | } else { 146 | [assetCollections addObject:assetCollection]; 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | self.assetCollections = [assetCollections copy]; 154 | } 155 | 156 | - (PHFetchResult *)assetsInAssetCollection:(PHAssetCollection *)collection { 157 | PHFetchOptions *options = [[PHFetchOptions alloc] init]; 158 | options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes]; 159 | options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; 160 | return [PHAsset fetchAssetsInAssetCollection:(PHAssetCollection *)collection options:options]; 161 | } 162 | 163 | #pragma mark - PHPhotoLibraryChangeObserver 164 | 165 | - (void)photoLibraryDidChange:(PHChange *)changeInstance { 166 | /* 167 | Change notifications may be made on a background queue. Re-dispatch to the 168 | main queue before acting on the change as we'll be updating the UI. 169 | */ 170 | dispatch_async(dispatch_get_main_queue(), ^{ 171 | // Loop through the section fetch results, replacing any fetch results that have been updated. 172 | NSMutableArray *updatedSectionFetchResults = [self.fetchResults mutableCopy]; 173 | __block BOOL reloadRequired = NO; 174 | 175 | [self.fetchResults enumerateObjectsUsingBlock:^(PHFetchResult *collectionsFetchResult, NSUInteger index, BOOL *stop) { 176 | PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult]; 177 | 178 | if (changeDetails != nil) { 179 | [updatedSectionFetchResults replaceObjectAtIndex:index withObject:[changeDetails fetchResultAfterChanges]]; 180 | reloadRequired = YES; 181 | } 182 | }]; 183 | 184 | if (reloadRequired) { 185 | self.fetchResults = updatedSectionFetchResults; 186 | [self.tableView reloadData]; 187 | } 188 | }); 189 | } 190 | 191 | #pragma mark - UITableViewDataSource 192 | 193 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 194 | return self.assetCollections.count; 195 | } 196 | 197 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 198 | PHAssetCollection *assetCollection = self.assetCollections[indexPath.row]; 199 | PHFetchResult *assets = [self assetsInAssetCollection:assetCollection]; 200 | 201 | // Dequeue an SCAlbumsViewCell. 202 | FTAlbumsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:FTAlbumsViewCellReuseIdentifier]; 203 | if (cell == nil) { 204 | cell = [[FTAlbumsViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:FTAlbumsViewCellReuseIdentifier]; 205 | } 206 | NSString *currentAlbumIdentifier = [NSString stringWithFormat:@"section -> %zd, row -> %zd", indexPath.section, indexPath.row]; 207 | cell.representedAlbumIdentifier = currentAlbumIdentifier; 208 | 209 | // Set the cell's title. 210 | NSString *text = assetCollection.localizedTitle; 211 | NSMutableAttributedString *attrStrM = [[NSMutableAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:16]}]; 212 | [attrStrM appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@" (%zd)", assets.count] attributes:@{NSForegroundColorAttributeName : [UIColor grayColor]}]]; 213 | cell.textLabel.attributedText = [attrStrM copy]; 214 | 215 | // Request an image for the collection from the PHCachingImageManager. 216 | CGFloat scale = [UIScreen mainScreen].scale; 217 | PHAsset *asset = assets.firstObject; 218 | [self.imageManager requestImageForAsset:asset 219 | targetSize:CGSizeMake(self.tableView.rowHeight * scale, self.tableView.rowHeight * scale) 220 | contentMode:PHImageContentModeAspectFill 221 | options:nil 222 | resultHandler:^(UIImage *result, NSDictionary *info) { 223 | // Set the cell's thumbnail image if it's still showing the same album. 224 | if ([cell.representedAlbumIdentifier isEqualToString:currentAlbumIdentifier]) { 225 | cell.thumbnailView.image = result; 226 | } 227 | }]; 228 | 229 | return cell; 230 | } 231 | 232 | #pragma mark - UITableViewDelegate 233 | 234 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 235 | PHAssetCollection *assetCollection = self.assetCollections[indexPath.row]; 236 | 237 | FTGridViewController *gridViewController = [[FTGridViewController alloc] initWithPicker:[self picker]]; 238 | gridViewController.assets = [self assetsInAssetCollection:assetCollection]; 239 | gridViewController.title = assetCollection.localizedTitle; 240 | 241 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 242 | [self.navigationController pushViewController:gridViewController animated:YES]; 243 | } 244 | @end 245 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTAssetsImageManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTImageManager.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import 10 | @import Photos; 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FTAssetsImageManager : NSObject 14 | @property (nonatomic, strong) PHCachingImageManager *phCachingImageManager; 15 | 16 | + (instancetype)sharedInstance; 17 | - (void)removeAllObjects; 18 | - (void)requestImageWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contextMode options:(PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler; 19 | 20 | NS_ASSUME_NONNULL_END 21 | @end 22 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTAssetsImageManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTImageManager.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTAssetsImageManager.h" 10 | 11 | @interface FTAssetsImageManager () 12 | @property (nonatomic, strong) NSCache *imageCache; 13 | 14 | @end 15 | 16 | 17 | @implementation FTAssetsImageManager 18 | - (void)removeAllObjects { 19 | [self.imageCache removeAllObjects]; 20 | if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) { 21 | [self.phCachingImageManager stopCachingImagesForAllAssets]; 22 | } 23 | 24 | 25 | } 26 | 27 | + (instancetype)sharedInstance { 28 | static FTAssetsImageManager *_instance; 29 | static dispatch_once_t onceToken; 30 | dispatch_once(&onceToken, ^{ 31 | _instance = [[FTAssetsImageManager alloc]init]; 32 | }); 33 | return _instance; 34 | } 35 | 36 | 37 | 38 | - (void)requestImageWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contextMode options:(PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *, NSDictionary *))resultHandler { 39 | NSString *ID = [self identifierWithAssset:asset targetSize:targetSize]; 40 | if ([self.imageCache objectForKey:ID]) { 41 | resultHandler([self.imageCache objectForKey:ID], nil); 42 | return; 43 | } else { 44 | [self.phCachingImageManager requestImageForAsset:asset targetSize:targetSize contentMode:contextMode options:options resultHandler:^(UIImage *result, NSDictionary *info) { 45 | // 排除取消,错误,低清图三种情况,即已经获取到了高清图时,把这张高清图缓存到 _previewImage 中 46 | BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]; 47 | if (downloadFinined) { 48 | [self.imageCache setObject:result forKey:ID]; 49 | } 50 | if (resultHandler) { 51 | resultHandler(result, info); 52 | } 53 | }]; 54 | return; 55 | } 56 | } 57 | 58 | - (NSString *)identifierWithAssset:(PHAsset *)asset targetSize:(CGSize)targetSize { 59 | return [NSString stringWithFormat:@"%@%@",asset.localIdentifier, NSStringFromCGSize(targetSize)]; 60 | } 61 | 62 | - (NSCache *)imageCache { 63 | if (!_imageCache) { 64 | _imageCache = [NSCache new]; 65 | } 66 | return _imageCache; 67 | } 68 | 69 | - (PHCachingImageManager *)phCachingImageManager { 70 | if (!_phCachingImageManager) { 71 | _phCachingImageManager = [PHCachingImageManager new]; 72 | } 73 | return _phCachingImageManager; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTBadgeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTBadgeView.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, FTBadgeViewAlignment) { 12 | FTBadgeViewAlignmentLeft = 0, 13 | FTBadgeViewAlignmentCenter, 14 | FTBadgeViewAlignmentRight 15 | }; 16 | 17 | typedef NS_ENUM(NSInteger, FTBadgeViewType) { 18 | FTBadgeViewTypeDefault = 0, 19 | FTBadgeViewTypeWhiteBorder 20 | }; 21 | 22 | @interface FTBadgeView : UIView 23 | 24 | /** 25 | * A rectangle defining the frame of the SCBadgeView object. The size components of this rectangle are ignored. 26 | */ 27 | - (instancetype)initWithFrame:(CGRect)frame alignment:(FTBadgeViewAlignment)alignment type:(FTBadgeViewType)type NS_DESIGNATED_INITIALIZER; 28 | 29 | - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 30 | 31 | #if TARGET_INTERFACE_BUILDER 32 | @property (nonatomic, assign) IBInspectable NSInteger alignment; 33 | @property (nonatomic, assign) IBInspectable NSInteger type; 34 | #else 35 | @property (nonatomic, assign, readonly) FTBadgeViewAlignment alignment; 36 | @property (nonatomic, assign, readonly) FTBadgeViewType type; 37 | #endif 38 | 39 | @property (nonatomic, assign) IBInspectable NSInteger number; 40 | @property (nonatomic, strong) IBInspectable UIColor *backgroundColor; 41 | @property (nonatomic, strong) IBInspectable UIColor *textColor; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTBadgeView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTBadgeView.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTBadgeView.h" 10 | 11 | @interface UIView(easy) 12 | 13 | @property (nonatomic, assign) CGFloat x; 14 | @property (nonatomic, assign) CGSize size; 15 | 16 | @end 17 | 18 | @implementation UIView(easy) 19 | 20 | - (CGFloat)x { 21 | return self.frame.origin.x; 22 | } 23 | 24 | - (void)setX:(CGFloat)x { 25 | CGRect frame = self.frame; 26 | frame.origin.x = x; 27 | self.frame = frame; 28 | } 29 | 30 | - (CGSize)size { 31 | return self.frame.size; 32 | } 33 | 34 | - (void)setSize:(CGSize)size { 35 | CGRect frame = self.frame; 36 | frame.size = size; 37 | self.frame = frame; 38 | } 39 | 40 | @end 41 | 42 | static CGSize const FTBadgeViewSize = {17, 17}; 43 | 44 | @implementation FTBadgeView 45 | { 46 | UIButton *_button; 47 | } 48 | 49 | #pragma mark - Life Cycle 50 | 51 | - (instancetype)initWithFrame:(CGRect)frame { 52 | return [self initWithFrame:frame alignment:FTBadgeViewAlignmentLeft type:FTBadgeViewTypeDefault]; 53 | } 54 | 55 | - (instancetype)initWithFrame:(CGRect)frame alignment:(FTBadgeViewAlignment)alignment type:(FTBadgeViewType)type { 56 | _alignment = alignment; 57 | _type = type; 58 | if (self = [super initWithFrame:frame]) { 59 | [self initializeSubViews]; 60 | } 61 | return self; 62 | } 63 | 64 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 65 | if (self = [super initWithCoder:aDecoder]) { 66 | [self initializeSubViews]; 67 | } 68 | return self; 69 | } 70 | 71 | - (void)initializeSubViews { 72 | self.hidden = YES; 73 | _button = [UIButton buttonWithType:UIButtonTypeCustom]; 74 | _button.adjustsImageWhenHighlighted = NO; 75 | _button.titleLabel.font = [UIFont systemFontOfSize:10]; 76 | [_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 77 | [_button setBackgroundColor:[UIColor colorWithRed:255 / 255.0 green:102 / 255.0 blue:102 / 255.0 alpha:1]]; 78 | _button.layer.cornerRadius = FTBadgeViewSize.height / 2; 79 | _button.clipsToBounds = YES; 80 | [self addSubview:_button]; 81 | 82 | switch (self.type) { 83 | case FTBadgeViewTypeDefault: 84 | 85 | break; 86 | case FTBadgeViewTypeWhiteBorder: 87 | [_button setImage:[UIImage imageNamed:@"shop_users"] forState:UIControlStateNormal]; 88 | [_button setTitleEdgeInsets:UIEdgeInsetsMake(0, 0, 0, -1)]; 89 | _button.layer.borderColor = [[UIColor whiteColor] CGColor]; 90 | _button.layer.borderWidth = 1; 91 | break; 92 | } 93 | } 94 | 95 | #pragma mark Setter 96 | 97 | - (void)setNumber:(NSInteger)number { 98 | if (_number != number) { 99 | _number = number; 100 | NSString *text; 101 | if (number > 0) { 102 | self.hidden = NO; 103 | if (number < 1000) { 104 | text = [NSString stringWithFormat:@"%zd", number]; 105 | } else { 106 | text = [NSString stringWithFormat:@"%.1fk", number / 1000.0]; 107 | } 108 | [_button setTitle:text forState:UIControlStateNormal]; 109 | [_button.titleLabel sizeToFit]; 110 | CGFloat width = _button.titleLabel.frame.size.width; 111 | 112 | switch (self.type) { 113 | case FTBadgeViewTypeDefault: 114 | if (FTBadgeViewSize.height >= width) { 115 | width = FTBadgeViewSize.height; 116 | } else { 117 | width += 6; 118 | } 119 | break; 120 | case FTBadgeViewTypeWhiteBorder: 121 | width += _button.currentImage.size.width + 7; 122 | break; 123 | } 124 | 125 | switch (_alignment) { 126 | case FTBadgeViewAlignmentLeft: 127 | // do nothing 128 | break; 129 | case FTBadgeViewAlignmentCenter: 130 | self.x -= (width - FTBadgeViewSize.width) / 2; 131 | break; 132 | case FTBadgeViewAlignmentRight: 133 | self.x -= width - FTBadgeViewSize.width; 134 | break; 135 | } 136 | self.size = CGSizeMake(width, FTBadgeViewSize.height); 137 | _button.size = self.size; 138 | } else { 139 | self.hidden = YES; 140 | text = @""; 141 | } 142 | } 143 | } 144 | 145 | - (void)setBackgroundColor:(UIColor *)backgroundColor { 146 | if (_backgroundColor != backgroundColor) { 147 | _backgroundColor = backgroundColor; 148 | [_button setBackgroundImage:[self backgroundImageWithColor:backgroundColor] forState:UIControlStateNormal]; 149 | } 150 | } 151 | 152 | - (void)setTextColor:(UIColor *)textColor { 153 | if (_textColor != textColor) { 154 | _textColor = textColor; 155 | [_button setTitleColor:textColor forState:UIControlStateNormal]; 156 | } 157 | } 158 | 159 | #pragma mark - Private Method 160 | 161 | - (UIImage *)backgroundImageWithColor:(UIColor *)color { 162 | CGSize size = FTBadgeViewSize; 163 | UIGraphicsBeginImageContext(size); 164 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 165 | CGContextSetFillColorWithColor(ctx, [color CGColor]); 166 | CGContextFillRect(ctx, CGRectMake(0, 0, size.width, size.height)); 167 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 168 | UIGraphicsEndImageContext(); 169 | 170 | UIGraphicsBeginImageContextWithOptions(image.size, NO , 0); 171 | ctx = UIGraphicsGetCurrentContext(); 172 | CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, image.size.width, image.size.height)); 173 | CGContextClip(ctx); 174 | CGContextStrokePath(ctx); 175 | [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; 176 | UIImage *circleImage = UIGraphicsGetImageFromCurrentImageContext(); 177 | UIGraphicsEndImageContext(); 178 | 179 | return [circleImage resizableImageWithCapInsets:UIEdgeInsetsMake(0, image.size.width / 2 - 0.5, 0, image.size.width / 2 - 0.5) resizingMode:UIImageResizingModeStretch]; 180 | } 181 | 182 | @end 183 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTBrowserViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTBrowserCell.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/30. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern CGFloat const FTBrowserRightMargin; 12 | 13 | @interface FTBrowserViewCell : UICollectionViewCell 14 | 15 | 16 | - (void)setImage:(UIImage *)image; 17 | @end 18 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTBrowserViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTBrowserCell.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/30. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTBrowserViewCell.h" 10 | 11 | 12 | CGFloat const FTBrowserRightMargin = 20; 13 | @interface FTBrowserViewCell () 14 | @property (nonatomic, strong) UIImageView *imageView; 15 | 16 | @end 17 | 18 | @implementation FTBrowserViewCell 19 | - (void)prepareForReuse { 20 | [super prepareForReuse]; 21 | self.imageView.image = nil; 22 | } 23 | 24 | 25 | - (instancetype)initWithFrame:(CGRect)frame { 26 | if (self = [super initWithFrame:frame]) { 27 | 28 | 29 | _imageView = [UIImageView new]; 30 | _imageView.frame = self.contentView.bounds; 31 | 32 | _imageView.contentMode = UIViewContentModeScaleAspectFill; 33 | 34 | [self.contentView addSubview:_imageView]; 35 | 36 | } 37 | return self; 38 | } 39 | 40 | - (void)setImage:(UIImage *)image { 41 | self.imageView.image = image; 42 | self.imageView.frame = [self imageViewRectWithImageSize:image.size]; 43 | } 44 | 45 | - (CGRect)imageViewRectWithImageSize:(CGSize)imageSize { 46 | CGFloat heightRatio = imageSize.height / [UIScreen mainScreen].bounds.size.height; 47 | CGFloat widthRatio = imageSize.width / [UIScreen mainScreen].bounds.size.width; 48 | CGSize size = CGSizeZero; 49 | if (heightRatio > 1 && widthRatio <= 1) { 50 | size = [self ratioSize:imageSize ratio:heightRatio]; 51 | } 52 | if (heightRatio <= 1 && widthRatio > 1) { 53 | size = [self ratioSize:imageSize ratio:widthRatio]; 54 | } 55 | size = [self ratioSize:imageSize ratio:MAX(heightRatio, widthRatio)]; 56 | CGFloat x = ([UIScreen mainScreen].bounds.size.width - size.width) / 2; 57 | CGFloat y = ([UIScreen mainScreen].bounds.size.height - size.height) / 2; 58 | return CGRectMake(x, y, size.width, size.height); 59 | } 60 | 61 | - (CGSize)ratioSize:(CGSize)originSize ratio:(CGFloat)ratio { 62 | return CGSizeMake(originSize.width / ratio, originSize.height / ratio); 63 | } 64 | 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTBrowserViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTBrowserViewController.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTImagePickerController.h" 10 | 11 | @interface FTBrowserViewController : UIViewController 12 | @property (nonatomic, strong) PHFetchResult *assets; 13 | @property (nonatomic, assign) NSInteger currentIndex; 14 | 15 | //回调Block 16 | @property (nonatomic, copy) void(^imageIsSelectedBlock)(NSInteger index, BOOL IsSelected); 17 | @property (nonatomic, copy) BOOL(^shouldSelectItemBlock)(); 18 | /** 19 | * 总是隐藏pageControl,默认为NO 20 | */ 21 | @property (nonatomic) BOOL alwaysPageControlHidden; 22 | 23 | - (instancetype)initWithPicker:(FTImagePickerController *)picker; 24 | @end 25 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTBrowserViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTBrowserViewController.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTBrowserViewController.h" 10 | #import "FTBadgeView.h" 11 | #import "FTBrowserViewCell.h" 12 | #import "FTAssetsImageManager.h" 13 | 14 | #define kScreenWidth [UIScreen mainScreen].bounds.size.width 15 | #define kScreenHeight [UIScreen mainScreen].bounds.size.height 16 | @interface FTBrowserViewController () 17 | 18 | @property (nonatomic, weak) FTImagePickerController *picker; 19 | @property (nonatomic, strong) PHCachingImageManager *imageManager; 20 | @property (nonatomic, strong) FTBadgeView *badgeView; 21 | @property (nonatomic) CGRect previousPreheatRect; 22 | @property (nonatomic, strong) PHImageRequestOptions *options; 23 | 24 | @end 25 | 26 | 27 | @implementation FTBrowserViewController 28 | { 29 | UICollectionView *_collectionView; 30 | UIPageControl *_pageControl; 31 | UIButton *_selectionButton; 32 | CGFloat screenWidth; 33 | CGFloat screenHeight; 34 | } 35 | NSString * const FTBrowserViewCellIdentifier = @"FTBrowserViewCellIdentifier"; 36 | 37 | - (void)initializeCollectionView { 38 | 39 | CGRect frame = self.view.frame; 40 | frame.size.width += FTBrowserRightMargin; 41 | 42 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 43 | layout.itemSize = frame.size; 44 | layout.minimumInteritemSpacing = 0; 45 | layout.minimumLineSpacing = 0; 46 | layout.sectionInset = UIEdgeInsetsZero; 47 | layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 48 | 49 | _collectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout]; 50 | _collectionView.dataSource = self; 51 | _collectionView.delegate = self; 52 | [_collectionView registerClass:[FTBrowserViewCell class] forCellWithReuseIdentifier:FTBrowserViewCellIdentifier]; 53 | _collectionView.showsHorizontalScrollIndicator = NO; 54 | _collectionView.pagingEnabled = YES; 55 | _collectionView.backgroundColor = [UIColor clearColor]; 56 | _collectionView.contentOffset = CGPointMake(self.currentIndex * _collectionView.frame.size.width, 0); 57 | [self.view addSubview:_collectionView]; 58 | } 59 | 60 | - (void)initializePageControl { 61 | 62 | _pageControl = [[UIPageControl alloc] init]; 63 | [self setPageControlHidden:YES]; 64 | _pageControl.numberOfPages = self.assets.count; 65 | _pageControl.currentPage = self.currentIndex; 66 | CGPoint center = _pageControl.center; 67 | center.x = self.view.center.x; 68 | center.y = CGRectGetMaxY(self.view.frame) - _pageControl.frame.size.height / 2 - 20; 69 | _pageControl.center = center; 70 | [self.view addSubview:_pageControl]; 71 | 72 | } 73 | - (void)viewDidAppear:(BOOL)animated { 74 | [super viewDidAppear:animated]; 75 | _selectionButton.selected = [self imageIsSelectedWithIndex:self.currentIndex]; 76 | } 77 | - (void)viewDidLoad { 78 | [super viewDidLoad]; 79 | [self initializePageControl]; 80 | [self initializeCollectionView]; 81 | self.view.backgroundColor = [UIColor blackColor]; 82 | UIButton *selectionButton = [UIButton buttonWithType:UIButtonTypeCustom]; 83 | selectionButton.bounds = CGRectMake(0,0,27,27); 84 | selectionButton.contentMode = UIViewContentModeTopRight; 85 | selectionButton.adjustsImageWhenHighlighted = NO; 86 | [selectionButton setImage:[UIImage imageNamed:[@"FTImagePickerController.bundle" stringByAppendingPathComponent:@"tickw.png"]] forState:UIControlStateNormal]; 87 | [selectionButton setImage:[UIImage imageNamed:[@"FTImagePickerController.bundle" stringByAppendingPathComponent:@"tickH.png"]] forState:UIControlStateSelected]; 88 | selectionButton.hidden = NO; 89 | _selectionButton = selectionButton; 90 | [selectionButton addTarget:self action:@selector(didClickSelectionButton) forControlEvents:UIControlEventTouchUpInside]; 91 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:selectionButton]; 92 | } 93 | 94 | - (void)didClickSelectionButton { 95 | _selectionButton.selected = !_selectionButton.isSelected; 96 | NSAssert(self.imageIsSelectedBlock, @"imageIsSelectedBlock cannot be nil"); 97 | self.imageIsSelectedBlock(self.currentIndex, _selectionButton.isSelected); 98 | } 99 | 100 | - (instancetype)initWithPicker:(FTImagePickerController *)picker { 101 | 102 | if (self = [super init]) { 103 | self.picker = picker; 104 | self.options = [[PHImageRequestOptions alloc]init]; 105 | 106 | } 107 | return self; 108 | } 109 | 110 | - (void)setPageControlHidden:(BOOL)hidden { 111 | if (hidden) { 112 | _pageControl.hidden = YES; 113 | } else { 114 | if (self.assets.count > 1 && !self.alwaysPageControlHidden) { 115 | _pageControl.hidden = NO; 116 | } 117 | } 118 | } 119 | #pragma mark - collectionView dataSource 120 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 121 | return 1; 122 | } 123 | 124 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 125 | return self.assets.count; 126 | } 127 | 128 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 129 | FTBrowserViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:FTBrowserViewCellIdentifier forIndexPath:indexPath]; 130 | 131 | PHAsset *asset = self.assets[indexPath.item]; 132 | __weak typeof(cell) WeakCell = cell; 133 | __weak typeof(collectionView) weakCollectionView = collectionView; 134 | CGFloat scale = [UIScreen mainScreen].scale; 135 | [[FTAssetsImageManager sharedInstance]requestImageWithAsset:asset 136 | targetSize:CGSizeMake(kScreenWidth * scale, kScreenWidth * scale) 137 | contentMode:PHImageContentModeAspectFit options:self.options 138 | resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { 139 | [WeakCell setImage:result]; 140 | [weakCollectionView reloadItemsAtIndexPaths:@[indexPath]]; 141 | }]; 142 | 143 | //缓存旁边图片 144 | NSMutableArray *nums = [NSMutableArray array]; 145 | if (indexPath.item - 1 > 0) { 146 | [nums addObject:@(indexPath.item - 1)]; 147 | } 148 | if (indexPath.item + 1 < self.assets.count) { 149 | [nums addObject:@(indexPath.item + 1)]; 150 | } 151 | [self cacheImageWithIndex:nums.copy]; 152 | return cell; 153 | } 154 | 155 | - (void)cacheImageWithIndex:(NSArray *)indexs { 156 | NSMutableArray *temAssets = [NSMutableArray arrayWithCapacity:indexs.count]; 157 | for (NSNumber *num in indexs) { 158 | [temAssets addObject:self.assets[[num integerValue]]]; 159 | } 160 | CGFloat scale = [UIScreen mainScreen].scale; 161 | [[FTAssetsImageManager sharedInstance].phCachingImageManager startCachingImagesForAssets:temAssets.copy targetSize:CGSizeMake(kScreenWidth * scale, kScreenWidth * scale) contentMode:PHImageContentModeAspectFill options:self.options]; 162 | } 163 | 164 | #pragma mark scrollView delegate 165 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 166 | NSInteger index = (NSInteger)(scrollView.contentOffset.x / scrollView.bounds.size.width); 167 | if (self.currentIndex != index) { 168 | self.currentIndex = index; 169 | _selectionButton.selected = [self imageIsSelectedWithIndex:self.currentIndex]; 170 | NSAssert(self.shouldSelectItemBlock, @"shouldSelectItemBlock cannot be nil"); 171 | _selectionButton.enabled = self.shouldSelectItemBlock(); 172 | } 173 | } 174 | 175 | #pragma mark 判断是否是被选中的 176 | - (BOOL)imageIsSelectedWithIndex:(NSInteger)index { 177 | if ([self.picker.selectedAssets containsObject:self.assets[index]]) { 178 | return YES; 179 | } 180 | return NO; 181 | } 182 | 183 | @end 184 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTGridViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTGridViewCell.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface FTGridViewCell : UICollectionViewCell 12 | 13 | @property (nonatomic, strong, readonly) UIImageView *thumbnailView; 14 | @property (nonatomic, copy) NSString *representedAssetIdentifier; 15 | @property (nonatomic, weak) UIButton *selectionButton; 16 | // Selection overlay 17 | @property (nonatomic) BOOL allowsSelection; 18 | 19 | @property (nonatomic, copy) void(^itemSelectedBlock)(BOOL isSelected); 20 | @property (nonatomic, copy) BOOL(^shouldSelectItemBlock)(); 21 | @end 22 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTGridViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTGridViewCell.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTGridViewCell.h" 10 | 11 | @interface FTGridViewCell () 12 | 13 | //@property (nonatomic, weak) UIView *selectedCoverView; 14 | 15 | 16 | @property (nonatomic, getter=isBtnSelected) BOOL btnSelected; 17 | 18 | @end 19 | 20 | @implementation FTGridViewCell 21 | 22 | - (void)prepareForReuse { 23 | [super prepareForReuse]; 24 | self.thumbnailView.image = nil; 25 | } 26 | 27 | 28 | - (instancetype)initWithFrame:(CGRect)frame { 29 | if (self = [super initWithFrame:frame]) { 30 | 31 | CGFloat cellSize = self.contentView.bounds.size.width; 32 | 33 | _btnSelected = NO; 34 | 35 | _thumbnailView = [UIImageView new]; 36 | _thumbnailView.frame = CGRectMake(0, 0, cellSize, cellSize); 37 | _thumbnailView.contentMode = UIViewContentModeScaleAspectFill; 38 | _thumbnailView.clipsToBounds = YES; 39 | _thumbnailView.translatesAutoresizingMaskIntoConstraints = NO; 40 | _thumbnailView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 41 | [self.contentView addSubview:_thumbnailView]; 42 | 43 | } 44 | return self; 45 | } 46 | 47 | - (void)setAllowsSelection:(BOOL)allowsSelection { 48 | if (_allowsSelection != allowsSelection) { 49 | _allowsSelection = allowsSelection; 50 | 51 | if (_allowsSelection) { 52 | 53 | UIButton *selectionButton = [UIButton buttonWithType:UIButtonTypeCustom]; 54 | selectionButton.frame = CGRectMake(2*self.bounds.size.width/3, 0*self.bounds.size.width/3, self.bounds.size.width/3, self.bounds.size.width/3); 55 | selectionButton.contentMode = UIViewContentModeTopRight; 56 | selectionButton.adjustsImageWhenHighlighted = NO; 57 | [selectionButton setImage:nil forState:UIControlStateNormal]; 58 | selectionButton.translatesAutoresizingMaskIntoConstraints = NO; 59 | selectionButton.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 60 | [selectionButton setImage:[UIImage imageNamed:[@"FTImagePickerController.bundle" stringByAppendingPathComponent:@"tickw.png"]] forState:UIControlStateNormal]; 61 | [selectionButton setImage:[UIImage imageNamed:[@"FTImagePickerController.bundle" stringByAppendingPathComponent:@"tickH.png"]] forState:UIControlStateSelected]; 62 | selectionButton.hidden = NO; 63 | [self.contentView addSubview:selectionButton]; 64 | [selectionButton addTarget:self action:@selector(didClickSelectionButton) forControlEvents:UIControlEventTouchUpInside]; 65 | _selectionButton = selectionButton; 66 | 67 | } else { 68 | 69 | 70 | [_selectionButton removeFromSuperview]; 71 | } 72 | } 73 | } 74 | 75 | - (void)didClickSelectionButton { 76 | NSAssert(self.shouldSelectItemBlock, @"shouldSelectItemBlock cannot be nil"); 77 | if (self.shouldSelectItemBlock() || _selectionButton.isSelected) { 78 | self.selectionButton.selected = !self.selectionButton.isSelected; 79 | if (self.itemSelectedBlock) { 80 | self.itemSelectedBlock(self.selectionButton.isSelected); 81 | } 82 | } 83 | 84 | } 85 | 86 | - (void)setSelected:(BOOL)selected { 87 | [super setSelected:selected]; 88 | 89 | // if (self.allowsSelection) { 90 | // _selectedCoverView.hidden = !selected; 91 | // _selectionButton.selected = selected; 92 | // } 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTGridViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTGridViewController.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTImagePickerController.h" 10 | 11 | @interface FTGridViewController : UICollectionViewController 12 | @property (nonatomic, strong) PHFetchResult *assets; 13 | 14 | - (instancetype)initWithPicker:(FTImagePickerController *)picker; 15 | @end 16 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTGridViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTGridViewController.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTGridViewController.h" 10 | #import "FTImagePickerController.h" 11 | #import "FTAlbumsViewController.h" 12 | #import "FTGridViewCell.h" 13 | #import "FTBadgeView.h" 14 | #import "FTBrowserViewController.h" 15 | 16 | @implementation NSIndexSet (Convenience) 17 | 18 | - (NSArray *)aapl_indexPathsFromIndexesWithSection:(NSUInteger)section { 19 | NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count]; 20 | [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 21 | [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]]; 22 | }]; 23 | return indexPaths; 24 | } 25 | 26 | @end 27 | 28 | @implementation UICollectionView (Convenience) 29 | 30 | - (NSArray *)aapl_indexPathsForElementsInRect:(CGRect)rect { 31 | NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect]; 32 | if (allLayoutAttributes.count == 0) { return nil; } 33 | NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count]; 34 | for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) { 35 | NSIndexPath *indexPath = layoutAttributes.indexPath; 36 | [indexPaths addObject:indexPath]; 37 | } 38 | return indexPaths; 39 | } 40 | 41 | @end 42 | 43 | @interface FTGridViewController () 44 | 45 | @property (nonatomic, weak) FTImagePickerController *picker; 46 | @property (nonatomic, strong) PHCachingImageManager *imageManager; 47 | @property (nonatomic, strong) FTBadgeView *badgeView; 48 | @property (nonatomic) CGRect previousPreheatRect; 49 | 50 | @end 51 | 52 | static CGSize AssetGridThumbnailSize; 53 | NSString * const FTGridViewCellIdentifier = @"FTGridViewCellIdentifier"; 54 | @implementation FTGridViewController 55 | { 56 | CGFloat screenWidth; 57 | CGFloat screenHeight; 58 | } 59 | 60 | - (instancetype)initWithPicker:(FTImagePickerController *)picker { 61 | //Custom init. The picker contains custom information to create the FlowLayout 62 | 63 | 64 | screenWidth = CGRectGetWidth(picker.view.bounds); 65 | screenHeight = CGRectGetHeight(picker.view.bounds); 66 | 67 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 68 | layout.minimumInteritemSpacing = 1; 69 | NSInteger cellTotalUsableWidth = screenWidth - 3; 70 | layout.itemSize = CGSizeMake(cellTotalUsableWidth / 4, cellTotalUsableWidth / 4); 71 | CGFloat cellTotalUsedWidth = layout.itemSize.width * 4; 72 | CGFloat spaceTotalWidth = screenWidth - cellTotalUsedWidth; 73 | CGFloat spaceWidth = spaceTotalWidth / 3; 74 | layout.minimumLineSpacing = spaceWidth; 75 | 76 | if (self = [super initWithCollectionViewLayout:layout]) { 77 | self.picker = picker; 78 | CGFloat scale = [UIScreen mainScreen].scale; 79 | AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale); 80 | self.collectionView.allowsMultipleSelection = picker.allowsMultipleSelection; 81 | [self.collectionView registerClass:FTGridViewCell.class 82 | forCellWithReuseIdentifier:FTGridViewCellIdentifier]; 83 | } 84 | return self; 85 | } 86 | 87 | - (void)viewDidLoad { 88 | [super viewDidLoad]; 89 | 90 | self.imageManager = [[PHCachingImageManager alloc] init]; 91 | [self resetCachedAssets]; 92 | 93 | [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; 94 | 95 | self.collectionView.backgroundColor = [UIColor whiteColor]; 96 | self.collectionView.contentInset = UIEdgeInsetsMake(1, 0, 1, 0); 97 | 98 | if (self.picker.allowsMultipleSelection) { 99 | UIBarButtonItem *doneButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"完成" 100 | style:UIBarButtonItemStyleDone 101 | target:self.picker 102 | action:@selector(finishPickingAssets:)]; 103 | doneButtonItem.enabled = self.picker.selectedAssets.count > 0; 104 | 105 | self.badgeView = [[FTBadgeView alloc] init]; 106 | self.badgeView.number = self.picker.selectedAssets.count; 107 | UIBarButtonItem *badgeButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.badgeView]; 108 | 109 | self.navigationItem.rightBarButtonItems = @[doneButtonItem, badgeButtonItem]; 110 | } 111 | 112 | self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"" style:(UIBarButtonItemStylePlain) target:nil action:nil]; 113 | } 114 | 115 | - (void)viewDidAppear:(BOOL)animated { 116 | [super viewDidAppear:animated]; 117 | 118 | // Begin caching assets in and around collection view's visible rect. 119 | [self updateCachedAssets]; 120 | } 121 | 122 | - (void)dealloc { 123 | [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; 124 | } 125 | 126 | - (BOOL)shouldSelectItem { 127 | if (self.picker.maxMultipleCount > 0 && self.picker.maxMultipleCount == self.picker.selectedAssets.count) { 128 | if ([self.picker.delegate respondsToSelector:@selector(assetsPickerVontrollerDidOverrunMaxMultipleCount:)]) { 129 | [self.picker.delegate assetsPickerVontrollerDidOverrunMaxMultipleCount:self.picker]; 130 | } 131 | return NO; 132 | } else { 133 | return YES; 134 | } 135 | } 136 | 137 | #pragma mark - PHPhotoLibraryChangeObserver 138 | 139 | - (void)photoLibraryDidChange:(PHChange *)changeInstance { 140 | // Check if there are changes to the assets we are showing. 141 | PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assets]; 142 | if (collectionChanges == nil) { 143 | return; 144 | } 145 | 146 | /* 147 | Change notifications may be made on a background queue. Re-dispatch to the 148 | main queue before acting on the change as we'll be updating the UI. 149 | */ 150 | dispatch_async(dispatch_get_main_queue(), ^{ 151 | // Get the new fetch result. 152 | self.assets = [collectionChanges fetchResultAfterChanges]; 153 | 154 | UICollectionView *collectionView = self.collectionView; 155 | 156 | if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) { 157 | // Reload the collection view if the incremental diffs are not available 158 | [collectionView reloadData]; 159 | 160 | } else { 161 | /* 162 | Tell the collection view to animate insertions and deletions if we 163 | have incremental diffs. 164 | */ 165 | [collectionView performBatchUpdates:^{ 166 | NSIndexSet *removedIndexes = [collectionChanges removedIndexes]; 167 | if ([removedIndexes count] > 0) { 168 | [collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]]; 169 | } 170 | 171 | NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes]; 172 | if ([insertedIndexes count] > 0) { 173 | [collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]]; 174 | } 175 | 176 | NSIndexSet *changedIndexes = [collectionChanges changedIndexes]; 177 | if ([changedIndexes count] > 0) { 178 | [collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]]; 179 | } 180 | } completion:nil]; 181 | } 182 | 183 | [self resetCachedAssets]; 184 | }); 185 | } 186 | 187 | #pragma mark - UICollectionViewDataSource 188 | 189 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 190 | return self.assets.count; 191 | } 192 | 193 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 194 | PHAsset *asset = self.assets[indexPath.item]; 195 | 196 | // Dequeue an SCGridViewCell. 197 | FTGridViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:FTGridViewCellIdentifier forIndexPath:indexPath]; 198 | cell.representedAssetIdentifier = asset.localIdentifier; 199 | 200 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init]; 201 | options.resizeMode = PHImageRequestOptionsResizeModeFast; 202 | // Request an image for the asset from the PHCachingImageManager. 203 | [self.imageManager requestImageForAsset:asset 204 | targetSize:AssetGridThumbnailSize 205 | contentMode:PHImageContentModeAspectFill 206 | options:options 207 | resultHandler:^(UIImage *result, NSDictionary *info) { 208 | // Set the cell's thumbnail image if it's still showing the same asset. 209 | if ([cell.representedAssetIdentifier isEqualToString:asset.localIdentifier]) { 210 | cell.thumbnailView.image = result; 211 | } 212 | }]; 213 | 214 | __weak typeof(self) weakSelf = self; 215 | [cell setItemSelectedBlock:^(BOOL isSelected) { 216 | PHAsset *asset = weakSelf.assets[indexPath.item]; 217 | if (isSelected) { 218 | [weakSelf.picker selectAsset:asset]; 219 | } else { 220 | [weakSelf.picker deselectAsset:asset]; 221 | } 222 | }]; 223 | 224 | [cell setShouldSelectItemBlock:^BOOL(){ 225 | return [weakSelf shouldSelectItem]; 226 | }]; 227 | cell.allowsSelection = self.picker.allowsMultipleSelection; 228 | cell.selectionButton.selected = [self.picker.selectedAssets containsObject:asset]? YES : NO; 229 | return cell; 230 | } 231 | 232 | #pragma mark - UICollectionViewDelegate 233 | 234 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 235 | [collectionView deselectItemAtIndexPath:indexPath animated:NO]; 236 | FTBrowserViewController *browserVC = [[FTBrowserViewController alloc]initWithPicker:self.picker]; 237 | browserVC.assets = self.assets; 238 | browserVC.currentIndex = indexPath.item; 239 | 240 | __weak typeof(self) weakSelf = self; 241 | [browserVC setImageIsSelectedBlock:^(NSInteger index, BOOL isSelected) { 242 | FTGridViewCell *cell = (FTGridViewCell *)[weakSelf.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; 243 | cell.selectionButton.selected = isSelected; 244 | PHAsset *asset = self.assets[index]; 245 | if (isSelected) { 246 | [weakSelf.picker selectAsset:asset]; 247 | } else { 248 | [weakSelf.picker deselectAsset:asset]; 249 | } 250 | }]; 251 | 252 | [browserVC setShouldSelectItemBlock:^BOOL{ 253 | return [weakSelf shouldSelectItem]; 254 | }]; 255 | [self.navigationController pushViewController:browserVC animated:YES]; 256 | 257 | 258 | } 259 | 260 | 261 | 262 | #pragma mark - UIScrollViewDelegate 263 | 264 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 265 | // Update cached assets for the new visible area. 266 | [self updateCachedAssets]; 267 | } 268 | 269 | #pragma mark - Asset Caching 270 | 271 | - (void)resetCachedAssets { 272 | [self.imageManager stopCachingImagesForAllAssets]; 273 | self.previousPreheatRect = CGRectZero; 274 | } 275 | 276 | - (void)updateCachedAssets { 277 | BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil; 278 | if (!isViewVisible) { return; } 279 | 280 | // The preheat window is twice the height of the visible rect 281 | CGRect preheatRect = self.collectionView.bounds; 282 | preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect)); 283 | 284 | // If scrolled by a "reasonable" amount... 285 | CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect)); 286 | if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) { 287 | 288 | // Compute the assets to start caching and to stop caching. 289 | NSMutableArray *addedIndexPaths = [NSMutableArray array]; 290 | NSMutableArray *removedIndexPaths = [NSMutableArray array]; 291 | 292 | [self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) { 293 | NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:removedRect]; 294 | [removedIndexPaths addObjectsFromArray:indexPaths]; 295 | } addedHandler:^(CGRect addedRect) { 296 | NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect]; 297 | [addedIndexPaths addObjectsFromArray:indexPaths]; 298 | }]; 299 | 300 | NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths]; 301 | NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths]; 302 | 303 | [self.imageManager startCachingImagesForAssets:assetsToStartCaching 304 | targetSize:AssetGridThumbnailSize 305 | contentMode:PHImageContentModeAspectFill 306 | options:nil]; 307 | [self.imageManager stopCachingImagesForAssets:assetsToStopCaching 308 | targetSize:AssetGridThumbnailSize 309 | contentMode:PHImageContentModeAspectFill 310 | options:nil]; 311 | 312 | self.previousPreheatRect = preheatRect; 313 | } 314 | } 315 | 316 | - (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler { 317 | if (CGRectIntersectsRect(newRect, oldRect)) { 318 | CGFloat oldMaxY = CGRectGetMaxY(oldRect); 319 | CGFloat oldMinY = CGRectGetMinY(oldRect); 320 | CGFloat newMaxY = CGRectGetMaxY(newRect); 321 | CGFloat newMinY = CGRectGetMinY(newRect); 322 | 323 | if (newMaxY > oldMaxY) { 324 | CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY)); 325 | addedHandler(rectToAdd); 326 | } 327 | 328 | if (oldMinY > newMinY) { 329 | CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY)); 330 | addedHandler(rectToAdd); 331 | } 332 | 333 | if (newMaxY < oldMaxY) { 334 | CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY)); 335 | removedHandler(rectToRemove); 336 | } 337 | 338 | if (oldMinY < newMinY) { 339 | CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY)); 340 | removedHandler(rectToRemove); 341 | } 342 | } else { 343 | addedHandler(newRect); 344 | removedHandler(oldRect); 345 | } 346 | } 347 | 348 | - (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths { 349 | if (indexPaths.count == 0) { return nil; } 350 | 351 | NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count]; 352 | for (NSIndexPath *indexPath in indexPaths) { 353 | PHAsset *asset = self.assets[indexPath.item]; 354 | [assets addObject:asset]; 355 | } 356 | 357 | return assets; 358 | } 359 | 360 | @end 361 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTImageClipViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTImageClipViewController.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTImagePickerController.h" 10 | 11 | @interface FTImageClipViewController : UIViewController 12 | 13 | - (instancetype)initWithPicker:(FTImagePickerController *)picker; 14 | 15 | @property (nonatomic, strong) UIImage *image; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTImageClipViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTImageClipViewController.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTImageClipViewController.h" 10 | #import "UIImage+FTHelper.h" 11 | 12 | @interface FTImageClipViewController () 13 | 14 | @property (nonatomic, weak) FTImagePickerController *picker; 15 | @property (nonatomic, strong) UIScrollView *scrollView; 16 | @property (nonatomic, strong) UIImageView *imageView; 17 | 18 | @end 19 | 20 | @implementation FTImageClipViewController 21 | 22 | - (instancetype)initWithPicker:(FTImagePickerController *)picker { 23 | self.picker = picker; 24 | if (self = [super init]) { 25 | } 26 | return self; 27 | } 28 | 29 | - (void)viewDidLoad { 30 | [super viewDidLoad]; 31 | 32 | self.view.backgroundColor = [UIColor blackColor]; 33 | self.edgesForExtendedLayout = UIRectEdgeNone; 34 | 35 | CGSize screenSize = [UIScreen mainScreen].bounds.size; 36 | if (CGSizeEqualToSize(self.picker.cropSize, CGSizeZero)) { 37 | self.picker.cropSize = CGSizeMake(screenSize.width, screenSize.width); 38 | } 39 | 40 | self.scrollView = [[UIScrollView alloc] initWithFrame:[self centerFitRectWithContentSize:self.picker.cropSize containerSize:[UIScreen mainScreen].bounds.size]]; 41 | self.scrollView.showsVerticalScrollIndicator = NO; 42 | self.scrollView.showsHorizontalScrollIndicator = NO; 43 | self.scrollView.alwaysBounceVertical = YES; 44 | self.scrollView.alwaysBounceHorizontal = YES; 45 | self.scrollView.delegate = self; 46 | [self.view addSubview:self.scrollView]; 47 | 48 | self.imageView = [[UIImageView alloc] init]; 49 | self.imageView.contentMode = UIViewContentModeScaleAspectFill; 50 | self.imageView.clipsToBounds = YES; 51 | [self.scrollView addSubview:self.imageView]; 52 | PHAsset *asset = self.picker.selectedAssets.firstObject; 53 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init]; 54 | options.resizeMode = PHImageRequestOptionsResizeModeFast; 55 | [[PHCachingImageManager defaultManager] requestImageForAsset:asset 56 | targetSize:CGSizeMake(asset.pixelWidth, asset.pixelHeight) 57 | contentMode:PHImageContentModeDefault 58 | options:options 59 | resultHandler:^(UIImage *result, NSDictionary *info) { 60 | // 这里会调多次,需重置transform得出正确的frame 61 | self.imageView.transform = CGAffineTransformIdentity; 62 | self.imageView.image = result; 63 | [self.imageView sizeToFit]; 64 | CGFloat scaleWidth = self.scrollView.frame.size.width / self.imageView.frame.size.width; 65 | CGFloat scaleHeight = self.scrollView.frame.size.height / self.imageView.frame.size.height; 66 | if (self.imageView.frame.size.width <= self.scrollView.frame.size.width || 67 | self.imageView.frame.size.height <= self.scrollView.frame.size.height) { 68 | self.scrollView.maximumZoomScale = MAX(scaleWidth, scaleHeight); 69 | } else { 70 | self.scrollView.maximumZoomScale = 1; 71 | } 72 | self.scrollView.minimumZoomScale = MAX(scaleWidth, scaleHeight); 73 | self.scrollView.zoomScale = self.scrollView.minimumZoomScale; 74 | }]; 75 | 76 | // mask 77 | UIImageView *mask = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[@"SCImagePickerController.bundle" stringByAppendingPathComponent:@"photo_rule.png"]]]; 78 | mask.frame = self.scrollView.frame; 79 | [self.view addSubview:mask]; 80 | UIView *topMaskView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, self.scrollView.frame.origin.y)]; 81 | UIView *bottomMaskView = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.scrollView.frame), screenSize.width, topMaskView.frame.size.height)]; 82 | topMaskView.backgroundColor = [UIColor blackColor]; 83 | bottomMaskView.backgroundColor = [UIColor blackColor]; 84 | topMaskView.alpha = 0.7; 85 | bottomMaskView.alpha = 0.7; 86 | [self.view addSubview:topMaskView]; 87 | [self.view addSubview:bottomMaskView]; 88 | 89 | // button 90 | UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom]; 91 | [cancelButton addTarget:self action:@selector(cancelButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 92 | cancelButton.frame = CGRectMake(0, screenSize.height - 120, screenSize.width / 2, 120); 93 | [cancelButton setTitle:@"取消" forState:UIControlStateNormal]; 94 | [self.view addSubview:cancelButton]; 95 | 96 | UIButton *selectButton = [UIButton buttonWithType:UIButtonTypeCustom]; 97 | [selectButton addTarget:self action:@selector(selectButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 98 | selectButton.frame = CGRectMake(screenSize.width / 2, screenSize.height - 120, screenSize.width / 2, 120); 99 | [selectButton setTitle:@"选取" forState:UIControlStateNormal]; 100 | [self.view addSubview:selectButton]; 101 | } 102 | 103 | - (void)viewWillAppear:(BOOL)animated { 104 | [super viewWillAppear:animated]; 105 | 106 | [self.navigationController setNavigationBarHidden:YES animated:YES]; 107 | } 108 | 109 | - (void)viewWillDisappear:(BOOL)animated { 110 | [super viewWillDisappear:animated]; 111 | 112 | [self.navigationController setNavigationBarHidden:NO animated:YES]; 113 | } 114 | 115 | #pragma mark - Action 116 | 117 | - (void)cancelButtonPressed:(id)sender { 118 | [self.picker.selectedAssets removeObjectAtIndex:0]; 119 | [self.navigationController popViewControllerAnimated:YES]; 120 | } 121 | 122 | - (void)selectButtonPressed:(id)sender { 123 | if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didFinishPickingImage:)]) { 124 | [self.picker.delegate assetsPickerController:self.picker didFinishPickingImage:[self clibImage:self.imageView.image]]; 125 | } 126 | } 127 | 128 | #pragma mark - Private Method 129 | 130 | - (UIImage *)clibImage:(UIImage *)image { 131 | CGFloat dirveScale = [[UIScreen mainScreen] scale]; 132 | CGFloat scale = self.scrollView.zoomScale; 133 | CGPoint offset = self.scrollView.contentOffset; 134 | CGFloat orignalScale = scale * dirveScale / (self.scrollView.frame.size.width * dirveScale) * self.picker.cropSize.width; 135 | CGPoint orignalOffset = CGPointMake(offset.x * dirveScale, offset.y * dirveScale); 136 | CGRect cropRect = CGRectMake(orignalOffset.x, orignalOffset.y, self.picker.cropSize.width, self.picker.cropSize.height); 137 | UIImage *resultImage = [image crop:cropRect scale:orignalScale]; 138 | return resultImage; 139 | } 140 | 141 | - (CGRect)centerFitRectWithContentSize:(CGSize)contentSize containerSize:(CGSize)containerSize { 142 | CGFloat heightRatio = contentSize.height / containerSize.height; 143 | CGFloat widthRatio = contentSize.width / containerSize.width; 144 | CGSize size = CGSizeZero; 145 | if (heightRatio > 1 && widthRatio <= 1) { 146 | size = [self ratioSize:contentSize ratio:heightRatio]; 147 | } else if (heightRatio <= 1 && widthRatio > 1) { 148 | size = [self ratioSize:contentSize ratio:widthRatio]; 149 | } else { 150 | size = [self ratioSize:contentSize ratio:MAX(heightRatio, widthRatio)]; 151 | } 152 | CGFloat x = (containerSize.width - size.width) / 2; 153 | CGFloat y = (containerSize.height - size.height) / 2; 154 | return CGRectMake(x, y, size.width, size.height); 155 | } 156 | 157 | - (CGSize)ratioSize:(CGSize)originSize ratio:(CGFloat)ratio { 158 | return CGSizeMake(originSize.width / ratio, originSize.height / ratio); 159 | } 160 | 161 | #pragma mark - UIScrollViewDelegate 162 | 163 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { 164 | return self.imageView; 165 | } 166 | 167 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView { 168 | CGFloat offsetX = (scrollView.bounds.size.width > scrollView.contentSize.width) ? 169 | (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0; 170 | CGFloat offsetY = (scrollView.bounds.size.height > scrollView.contentSize.height) ? 171 | (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0; 172 | CGPoint actualCenter = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, 173 | scrollView.contentSize.height * 0.5 + offsetY); 174 | self.imageView.center = actualCenter; 175 | } 176 | 177 | 178 | @end 179 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTImagePickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FTImagePickerController.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | @import Photos; 11 | 12 | typedef NS_ENUM(NSInteger, FTImagePickerControllerSourceType) { 13 | FTImagePickerControllerSourceTypePhotoLibrary, 14 | FTImagePickerControllerSourceTypeSavedPhotosAlbum 15 | }; 16 | 17 | @protocol FTImagePickerControllerDelegate; 18 | 19 | @interface FTImagePickerController : UIViewController 20 | @property (nonatomic, strong) NSMutableArray *selectedAssets; 21 | 22 | @property (nonatomic) FTImagePickerControllerSourceType sourceType; 23 | 24 | @property (nonatomic, strong) NSArray *mediaTypes; // default value is an array containing PHAssetMediaTypeImage. 25 | 26 | @property (nonatomic) BOOL allowsMultipleSelection; // default value is NO. 27 | @property (nonatomic) NSInteger maxMultipleCount; // default is unlimited and value is 0. 28 | 29 | // These two properties are available when allowsMultipleSelection value is NO. 30 | @property (nonatomic) BOOL allowsEditing; // default value is NO. 31 | @property (nonatomic) CGSize cropSize; // default value is {[UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width} 32 | 33 | @property (nonatomic, strong) UINavigationController *navigationController; 34 | 35 | // Managing Asset Selection 36 | - (void)selectAsset:(PHAsset *)asset; 37 | - (void)deselectAsset:(PHAsset *)asset; 38 | 39 | // User finish Actions 40 | - (void)finishPickingAssets:(id)sender; 41 | - (void)dismiss:(id)sender; 42 | 43 | @property (nonatomic, weak) id delegate; 44 | 45 | @end 46 | 47 | @protocol FTImagePickerControllerDelegate 48 | 49 | @optional 50 | 51 | - (void)assetsPickerController:(FTImagePickerController *)picker didFinishPickingAssets:(NSArray *)assets; 52 | 53 | - (void)assetsPickerController:(FTImagePickerController *)picker didFinishPickingImages:(NSArray *)images; 54 | 55 | - (void)assetsPickerControllerDidCancel:(FTImagePickerController *)picker; 56 | 57 | - (void)assetsPickerVontrollerDidOverrunMaxMultipleCount:(FTImagePickerController *)picker; 58 | 59 | // This method is called when allowsMultipleSelection is NO and allowsEditing is YES. 60 | - (void)assetsPickerController:(FTImagePickerController *)picker didFinishPickingImage:(UIImage *)image; 61 | 62 | @end -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/FTImagePickerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FTImagePickerController.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "FTImagePickerController.h" 10 | #import "FTAlbumsViewController.h" 11 | #import "FTImageClipViewController.h" 12 | #import "FTBadgeView.h" 13 | #import "FTAssetsImageManager.h" 14 | 15 | @import Photos; 16 | @implementation FTImagePickerController 17 | - (void)dealloc { 18 | NSLog(@"dealloc"); 19 | 20 | // [[FTAssetsImageManager sharedInstance] removeAllObjects]; 21 | 22 | } 23 | 24 | - (instancetype)init { 25 | if (self = [super init]) { 26 | _selectedAssets = [[NSMutableArray alloc] init]; 27 | _mediaTypes = @[@(PHAssetMediaTypeImage)]; 28 | _navigationController = [[UINavigationController alloc] initWithRootViewController:[[FTAlbumsViewController alloc] init]]; 29 | _navigationController.navigationBar.barTintColor = [UIColor colorWithRed:71/255.0f green:71/255.0f blue:89/255.0f alpha:0.8f]; 30 | _navigationController.navigationBar.tintColor = [UIColor whiteColor]; 31 | _navigationController.navigationBar.titleTextAttributes = @{ 32 | NSForegroundColorAttributeName : [UIColor whiteColor], 33 | NSFontAttributeName : [UIFont systemFontOfSize:18] 34 | }; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)viewDidLoad { 40 | [super viewDidLoad]; 41 | 42 | [self.navigationController willMoveToParentViewController:self]; 43 | [self.navigationController.view setFrame:self.view.frame]; 44 | [self.view addSubview:self.navigationController.view]; 45 | [self addChildViewController:self.navigationController]; 46 | [self.navigationController didMoveToParentViewController:self]; 47 | } 48 | 49 | - (void)selectAsset:(PHAsset *)asset { 50 | [self.selectedAssets insertObject:asset atIndex:self.selectedAssets.count]; 51 | if (self.allowsMultipleSelection) { 52 | [self updateDoneButton]; 53 | } else { 54 | if (self.allowsEditing) { 55 | FTImageClipViewController *controller = [[FTImageClipViewController alloc] initWithPicker:self]; 56 | [self.navigationController pushViewController:controller animated:YES]; 57 | } else { 58 | [self finishPickingAssets:self]; 59 | } 60 | } 61 | } 62 | 63 | - (void)deselectAsset:(PHAsset *)asset { 64 | [self.selectedAssets removeObjectAtIndex:[self.selectedAssets indexOfObject:asset]]; 65 | [self updateDoneButton]; 66 | } 67 | 68 | - (void)finishPickingAssets:(id)sender { 69 | if ([self.delegate respondsToSelector:@selector(assetsPickerController:didFinishPickingAssets:)]) { 70 | [self.delegate assetsPickerController:self didFinishPickingAssets:self.selectedAssets]; 71 | } 72 | if ([self.delegate respondsToSelector:@selector(assetsPickerController:didFinishPickingImages:)]) { 73 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 74 | NSArray *arr = [self selectedImages]; 75 | dispatch_async(dispatch_get_main_queue(), ^{ 76 | [self.delegate assetsPickerController:self didFinishPickingImages:arr]; 77 | }); 78 | }); 79 | } 80 | } 81 | 82 | - (void)dismiss:(id)sender { 83 | if ([self.delegate respondsToSelector:@selector(assetsPickerControllerDidCancel:)]) { 84 | [self.delegate assetsPickerControllerDidCancel:self]; 85 | } 86 | [[FTAssetsImageManager sharedInstance]removeAllObjects]; 87 | [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; 88 | } 89 | 90 | - (void)updateDoneButton { 91 | UINavigationController *nav = (UINavigationController *)self.childViewControllers[0]; 92 | for (UIViewController *viewController in nav.viewControllers) { 93 | viewController.navigationItem.rightBarButtonItem.enabled = self.selectedAssets.count > 0; 94 | if (viewController.navigationItem.rightBarButtonItems.count > 1) { 95 | UIBarButtonItem *badgeButtonItem = viewController.navigationItem.rightBarButtonItems[1]; 96 | FTBadgeView *badgeView = badgeButtonItem.customView; 97 | badgeView.number = self.selectedAssets.count; 98 | } 99 | } 100 | } 101 | 102 | - (NSArray *)selectedImages { 103 | PHImageRequestOptions *options = [PHImageRequestOptions new]; 104 | options.synchronous = YES; 105 | NSMutableArray *images = [NSMutableArray arrayWithCapacity:self.selectedAssets.count]; 106 | for (PHAsset *asset in self.selectedAssets) { 107 | CGFloat scale = [UIScreen mainScreen].scale; 108 | [[FTAssetsImageManager sharedInstance].phCachingImageManager requestImageForAsset:asset targetSize:CGSizeMake([UIScreen mainScreen].bounds.size.width * scale, [UIScreen mainScreen].bounds.size.height * scale) contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { 109 | BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]; 110 | if (downloadFinined) { 111 | [images addObject:result]; 112 | } 113 | }]; 114 | } 115 | return images; 116 | } 117 | @end 118 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/UIImage+FTHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+FTHelper.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (FTHelper) 12 | + (CGSize)resizeForSend:(CGSize)size; 13 | 14 | - (UIImage *)crop:(CGRect)rect scale:(CGFloat)scale; 15 | @end 16 | -------------------------------------------------------------------------------- /FTImagePickerController/FTImagePickerController/UIImage+FTHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+FTHelper.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "UIImage+FTHelper.h" 10 | 11 | @implementation UIImage (FTHelper) 12 | 13 | + (CGSize)resizeForSend:(CGSize)size { 14 | 15 | CGSize targetSize = size; 16 | 17 | CGFloat regularLength = 1280; 18 | CGFloat regularFactor = 2; 19 | CGFloat factor = size.width >= size.height ? size.width / size.height : size.height / size.width; 20 | 21 | // 1.宽小于等于regularLength,高小于等于regularLength 22 | // 2.宽小于等于regularLength,高大于regularLength,且factor大于regularFactor 23 | // 3.宽大于regularLength,高小于等于regularLength,且factor大于regularFactor 24 | if ((size.width <= regularLength && size.height <= regularLength) || 25 | (size.width <= regularLength && size.height > regularLength && factor > regularFactor) || 26 | (size.width > regularLength && size.height <= regularLength && factor > regularFactor)) { 27 | // 保持尺寸 28 | } 29 | else { 30 | // 等比缩小 31 | // 按宽=regularLength等比缩小 32 | // 1.宽大于regularLength,高小于等于regularLength,且factor小于等于regularFactor 33 | // 2.宽大于regularLength,高大于regularLength,且宽大于等于高 34 | if ((size.width > regularLength && size.height <= regularLength && factor <= regularFactor) || 35 | (size.width > regularLength && size.height > regularLength && size.width >= size.height)) { 36 | targetSize = CGSizeMake(regularLength, regularLength * size.height / size.width); 37 | } 38 | // 按高=regularLength等比缩小 39 | // 1.宽小于等于regularLength,高大于regularLength,且factor小于等于regularFactor 40 | // 2.宽大于regularLength,高大于regularLength,且宽小于高 41 | else { 42 | targetSize = CGSizeMake(regularLength * size.width / size.height, regularLength); 43 | } 44 | } 45 | return targetSize; 46 | } 47 | 48 | - (UIImage*)crop:(CGRect)rect scale:(CGFloat)scale { 49 | CGPoint origin = CGPointMake(-rect.origin.x, -rect.origin.y); 50 | UIImage *image = nil; 51 | UIGraphicsBeginImageContext(CGSizeMake(rect.size.width, rect.size.height)); 52 | [self drawInRect:CGRectMake(origin.x, origin.y, self.size.width * scale, self.size.height * scale)]; 53 | image = UIGraphicsGetImageFromCurrentImageContext(); 54 | UIGraphicsEndImageContext(); 55 | return image; 56 | } 57 | @end 58 | -------------------------------------------------------------------------------- /FTImagePickerController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /FTImagePickerController/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /FTImagePickerController/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "FTImagePickerController.h" 11 | 12 | @interface ViewController () 13 | @property (nonatomic, strong) UIImageView *imageView; 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 100, 200, 200)]; 21 | [self.view addSubview:self.imageView]; 22 | } 23 | - (IBAction)didClickStart:(id)sender { 24 | FTImagePickerController *picker = [[FTImagePickerController alloc] init]; 25 | picker.delegate = self; 26 | 27 | picker.sourceType = FTImagePickerControllerSourceTypeSavedPhotosAlbum; 28 | 29 | picker.allowsMultipleSelection = YES; 30 | picker.maxMultipleCount = 10; 31 | 32 | picker.allowsEditing = YES; 33 | //返回图片大小 34 | picker.cropSize = CGSizeMake(750, 750); 35 | 36 | [self presentViewController:picker animated:YES completion:nil]; 37 | } 38 | 39 | #pragma mark - FTImagePickerControllerDelegate 40 | 41 | - (void)assetsPickerController:(FTImagePickerController *)picker didFinishPickingAssets:(NSArray *)assets { 42 | [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil]; 43 | // NSLog(@"当前选择图片集合 -> %@", assets); 44 | } 45 | - (void)assetsPickerController:(FTImagePickerController *)picker didFinishPickingImages:(NSArray *)images { 46 | NSLog(@"当前选择图片集合 -> %@", images); 47 | } 48 | - (void)assetsPickerController:(FTImagePickerController *)picker didFinishPickingImage:(UIImage *)image { 49 | [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil]; 50 | NSLog(@"当前编辑图片 -> %@", image); 51 | self.imageView.image = image; 52 | } 53 | 54 | - (void)assetsPickerControllerDidCancel:(FTImagePickerController *)picker { 55 | NSLog(@"结束选择图片"); 56 | } 57 | 58 | - (void)assetsPickerVontrollerDidOverrunMaxMultipleCount:(FTImagePickerController *)picker { 59 | NSLog(@"超过最大可选数量 -> %zd", picker.maxMultipleCount); 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /FTImagePickerController/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FTImagePickerController 4 | // 5 | // Created by 方焘 on 16/5/29. 6 | // Copyright © 2016年 taofang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 leo.fang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------