├── .gitignore ├── Cartfile ├── Cartfile.resolved ├── Images.xcassets ├── Contents.json ├── EmptyPlaceholder.imageset │ ├── Contents.json │ └── EmptyPlaceholder.pdf ├── checkmark_selected.imageset │ ├── Contents.json │ └── checkmark_select.pdf ├── checkmark_unselected.imageset │ ├── Contents.json │ └── checkmark.pdf ├── toolbar_highquality_checked.imageset │ ├── Contents.json │ └── toolbar_highquality_checked.pdf ├── toolbar_highquality_unchecked.imageset │ ├── Contents.json │ └── toolbar_highquality_unchecked.pdf └── toolbar_numberview_background.imageset │ ├── Contents.json │ └── toolbar_numberview_background.pdf ├── LICENSE ├── PhotoPicker.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── StormXX.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── PhotoPicker.xcscheme └── xcuserdata │ └── StormXX.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── PhotoPicker.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── StormXX.xcuserdatad │ ├── UserInterfaceState.xcuserstate │ └── xcdebugger │ └── Breakpoints_v2.xcbkptlist ├── PhotoPicker ├── AlbumCell.swift ├── AlbumsViewController.swift ├── AssetCell.swift ├── AssetsViewController.swift ├── CircleProgressView.swift ├── CommonExtension.swift ├── Info.plist ├── PhotoPicker.h ├── PhotoPicker.storyboard ├── PhotoPickerController.swift ├── PhotoPickerProtocol.swift ├── PhotoPickerThemeManager.swift ├── SlomoIconView.swift ├── ToolBarHighQualityButton.swift ├── ToolBarNumberView.swift ├── UIView+Frame.swift ├── VideoIconView.swift ├── VideoIndicatorView.swift └── constant.swift ├── PhotoPickerDemo.xcodeproj ├── PhotoPicker.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── StormXX.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── StormXX.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── StormXX.xcuserdatad │ └── xcschemes │ ├── PhotoPickerDemo.xcscheme │ └── xcschememanagement.plist ├── PhotoPickerDemo ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── README.md └── TBPhotoPicker.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/swift 2 | 3 | ### Swift ### 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData 11 | .swift-version 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | 23 | ## Other 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 64 | 65 | fastlane/report.xml 66 | fastlane/screenshots 67 | 68 | 69 | # Created by https://www.gitignore.io/api/swift 70 | 71 | ### Swift ### 72 | # Xcode 73 | # 74 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 75 | 76 | ## Build generated 77 | build/ 78 | DerivedData/ 79 | 80 | ## Various settings 81 | *.pbxuser 82 | !default.pbxuser 83 | *.mode1v3 84 | !default.mode1v3 85 | *.mode2v3 86 | !default.mode2v3 87 | *.perspectivev3 88 | !default.perspectivev3 89 | xcuserdata/ 90 | 91 | ## Other 92 | *.moved-aside 93 | *.xcuserstate 94 | 95 | ## Obj-C/Swift specific 96 | *.hmap 97 | *.ipa 98 | 99 | ## Playgrounds 100 | timeline.xctimeline 101 | playground.xcworkspace 102 | 103 | # Swift Package Manager 104 | # 105 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 106 | # Packages/ 107 | .build/ 108 | 109 | # CocoaPods 110 | # 111 | # We recommend against adding the Pods directory to your .gitignore. However 112 | # you should judge for yourself, the pros and cons are mentioned at: 113 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 114 | # 115 | # Pods/ 116 | 117 | # Carthage 118 | # 119 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 120 | # Carthage/Checkouts 121 | 122 | Carthage/Build 123 | 124 | # fastlane 125 | # 126 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 127 | # screenshots whenever they are needed. 128 | # For more information about the recommended setup visit: 129 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 130 | 131 | fastlane/report.xml 132 | fastlane/screenshots 133 | 134 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "teambition/PhotoBrowser" 2 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "onevcat/Kingfisher" "5.3.1" 2 | github "teambition/PhotoBrowser" "0.15.1" 3 | -------------------------------------------------------------------------------- /Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Images.xcassets/EmptyPlaceholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "EmptyPlaceholder.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Images.xcassets/EmptyPlaceholder.imageset/EmptyPlaceholder.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/Images.xcassets/EmptyPlaceholder.imageset/EmptyPlaceholder.pdf -------------------------------------------------------------------------------- /Images.xcassets/checkmark_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "checkmark_select.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Images.xcassets/checkmark_selected.imageset/checkmark_select.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/Images.xcassets/checkmark_selected.imageset/checkmark_select.pdf -------------------------------------------------------------------------------- /Images.xcassets/checkmark_unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "checkmark.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Images.xcassets/checkmark_unselected.imageset/checkmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/Images.xcassets/checkmark_unselected.imageset/checkmark.pdf -------------------------------------------------------------------------------- /Images.xcassets/toolbar_highquality_checked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "toolbar_highquality_checked.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Images.xcassets/toolbar_highquality_checked.imageset/toolbar_highquality_checked.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/Images.xcassets/toolbar_highquality_checked.imageset/toolbar_highquality_checked.pdf -------------------------------------------------------------------------------- /Images.xcassets/toolbar_highquality_unchecked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "toolbar_highquality_unchecked.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Images.xcassets/toolbar_highquality_unchecked.imageset/toolbar_highquality_unchecked.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/Images.xcassets/toolbar_highquality_unchecked.imageset/toolbar_highquality_unchecked.pdf -------------------------------------------------------------------------------- /Images.xcassets/toolbar_numberview_background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "toolbar_numberview_background.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Images.xcassets/toolbar_numberview_background.imageset/toolbar_numberview_background.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/Images.xcassets/toolbar_numberview_background.imageset/toolbar_numberview_background.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Teambition 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 | -------------------------------------------------------------------------------- /PhotoPicker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A100447E1ECDAE640050A8F0 /* PhotoPickerThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A100447D1ECDAE640050A8F0 /* PhotoPickerThemeManager.swift */; }; 11 | AC2A494E21AAF46000F1CF46 /* CircleProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2A494D21AAF45F00F1CF46 /* CircleProgressView.swift */; }; 12 | EB15452E1C856C920055715B /* ToolBarHighQualityButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB15452D1C856C920055715B /* ToolBarHighQualityButton.swift */; }; 13 | EB3E4A611C8031D800175777 /* AssetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3E4A601C8031D800175777 /* AssetCell.swift */; }; 14 | EB3E4A651C8036E200175777 /* AssetsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3E4A641C8036E200175777 /* AssetsViewController.swift */; }; 15 | EB48DC301C7F0EB3009F5898 /* CommonExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB48DC2F1C7F0EB3009F5898 /* CommonExtension.swift */; }; 16 | EBA70ADA1C7EA9320089ECD7 /* PhotoPicker.h in Headers */ = {isa = PBXBuildFile; fileRef = EBA70AD91C7EA9320089ECD7 /* PhotoPicker.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | EBA70B071C7ED26F0089ECD7 /* PhotoPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EBA70B061C7ED26F0089ECD7 /* PhotoPicker.storyboard */; }; 18 | EBA70B091C7ED6D60089ECD7 /* AlbumCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA70B081C7ED6D60089ECD7 /* AlbumCell.swift */; }; 19 | EBA70B0B1C7ED8030089ECD7 /* constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA70B0A1C7ED8030089ECD7 /* constant.swift */; }; 20 | EBA70B0D1C7ED91E0089ECD7 /* PhotoPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA70B0C1C7ED91E0089ECD7 /* PhotoPickerController.swift */; }; 21 | EBA70B101C7EDB3F0089ECD7 /* PhotoPickerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA70B0F1C7EDB3F0089ECD7 /* PhotoPickerProtocol.swift */; }; 22 | EBA70B121C7EE1580089ECD7 /* AlbumsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA70B111C7EE1580089ECD7 /* AlbumsViewController.swift */; }; 23 | EBA70B141C7F03580089ECD7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBA70B131C7F03580089ECD7 /* Images.xcassets */; }; 24 | EBCD67341C843E8600146FDB /* ToolBarNumberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCD67331C843E8600146FDB /* ToolBarNumberView.swift */; }; 25 | EBD3E13D1D8933220005F273 /* PhotoBrowser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EBD3E13C1D8933220005F273 /* PhotoBrowser.framework */; }; 26 | EBD4A75D1D1924C7003280BF /* VideoIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD4A75C1D1924C7003280BF /* VideoIconView.swift */; }; 27 | EBD4A75F1D192595003280BF /* UIView+Frame.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD4A75E1D192595003280BF /* UIView+Frame.swift */; }; 28 | EBD4A7611D1926C9003280BF /* SlomoIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD4A7601D1926C9003280BF /* SlomoIconView.swift */; }; 29 | EBD4A7631D1927E1003280BF /* VideoIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD4A7621D1927E1003280BF /* VideoIndicatorView.swift */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | A100447D1ECDAE640050A8F0 /* PhotoPickerThemeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoPickerThemeManager.swift; sourceTree = ""; }; 34 | AC2A494D21AAF45F00F1CF46 /* CircleProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleProgressView.swift; sourceTree = ""; }; 35 | EB15452D1C856C920055715B /* ToolBarHighQualityButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolBarHighQualityButton.swift; sourceTree = ""; }; 36 | EB3E4A601C8031D800175777 /* AssetCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetCell.swift; sourceTree = ""; }; 37 | EB3E4A641C8036E200175777 /* AssetsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetsViewController.swift; sourceTree = ""; }; 38 | EB48DC2F1C7F0EB3009F5898 /* CommonExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonExtension.swift; sourceTree = ""; }; 39 | EBA70AD61C7EA9320089ECD7 /* PhotoPicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PhotoPicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | EBA70AD91C7EA9320089ECD7 /* PhotoPicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhotoPicker.h; sourceTree = ""; }; 41 | EBA70ADB1C7EA9320089ECD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | EBA70B061C7ED26F0089ECD7 /* PhotoPicker.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = PhotoPicker.storyboard; sourceTree = ""; }; 43 | EBA70B081C7ED6D60089ECD7 /* AlbumCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumCell.swift; sourceTree = ""; }; 44 | EBA70B0A1C7ED8030089ECD7 /* constant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = constant.swift; sourceTree = ""; }; 45 | EBA70B0C1C7ED91E0089ECD7 /* PhotoPickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoPickerController.swift; sourceTree = ""; }; 46 | EBA70B0F1C7EDB3F0089ECD7 /* PhotoPickerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoPickerProtocol.swift; sourceTree = ""; }; 47 | EBA70B111C7EE1580089ECD7 /* AlbumsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumsViewController.swift; sourceTree = ""; }; 48 | EBA70B131C7F03580089ECD7 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ../Images.xcassets; sourceTree = ""; }; 49 | EBCD67331C843E8600146FDB /* ToolBarNumberView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolBarNumberView.swift; sourceTree = ""; }; 50 | EBD3E13C1D8933220005F273 /* PhotoBrowser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PhotoBrowser.framework; path = Carthage/Build/iOS/PhotoBrowser.framework; sourceTree = ""; }; 51 | EBD4A75C1D1924C7003280BF /* VideoIconView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoIconView.swift; sourceTree = ""; }; 52 | EBD4A75E1D192595003280BF /* UIView+Frame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Frame.swift"; sourceTree = ""; }; 53 | EBD4A7601D1926C9003280BF /* SlomoIconView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlomoIconView.swift; sourceTree = ""; }; 54 | EBD4A7621D1927E1003280BF /* VideoIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoIndicatorView.swift; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | EBA70AD21C7EA9320089ECD7 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | EBD3E13D1D8933220005F273 /* PhotoBrowser.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | EB5A63DC1D8924610093ACDC /* Framework */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | EBD3E13C1D8933220005F273 /* PhotoBrowser.framework */, 73 | ); 74 | name = Framework; 75 | sourceTree = ""; 76 | }; 77 | EBA70ACC1C7EA9320089ECD7 = { 78 | isa = PBXGroup; 79 | children = ( 80 | EB5A63DC1D8924610093ACDC /* Framework */, 81 | EBA70AD81C7EA9320089ECD7 /* PhotoPicker */, 82 | EBA70AD71C7EA9320089ECD7 /* Products */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | EBA70AD71C7EA9320089ECD7 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | EBA70AD61C7EA9320089ECD7 /* PhotoPicker.framework */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | EBA70AD81C7EA9320089ECD7 /* PhotoPicker */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | EBA70AD91C7EA9320089ECD7 /* PhotoPicker.h */, 98 | EBA70B0A1C7ED8030089ECD7 /* constant.swift */, 99 | A100447D1ECDAE640050A8F0 /* PhotoPickerThemeManager.swift */, 100 | EBA70B0E1C7EDA6E0089ECD7 /* Extensions */, 101 | EBA70B051C7EAB660089ECD7 /* Views */, 102 | EBA70B041C7EAB5F0089ECD7 /* Controllers */, 103 | EBA70B031C7EAB4E0089ECD7 /* Resources */, 104 | EBA70ADB1C7EA9320089ECD7 /* Info.plist */, 105 | ); 106 | path = PhotoPicker; 107 | sourceTree = ""; 108 | }; 109 | EBA70B031C7EAB4E0089ECD7 /* Resources */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | EBA70B061C7ED26F0089ECD7 /* PhotoPicker.storyboard */, 113 | EBA70B131C7F03580089ECD7 /* Images.xcassets */, 114 | ); 115 | name = Resources; 116 | sourceTree = ""; 117 | }; 118 | EBA70B041C7EAB5F0089ECD7 /* Controllers */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | EBA70B0C1C7ED91E0089ECD7 /* PhotoPickerController.swift */, 122 | EBA70B111C7EE1580089ECD7 /* AlbumsViewController.swift */, 123 | EB3E4A641C8036E200175777 /* AssetsViewController.swift */, 124 | ); 125 | name = Controllers; 126 | sourceTree = ""; 127 | }; 128 | EBA70B051C7EAB660089ECD7 /* Views */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | EBD4A75B1D1924A7003280BF /* IconViews */, 132 | EBA70B081C7ED6D60089ECD7 /* AlbumCell.swift */, 133 | EB3E4A601C8031D800175777 /* AssetCell.swift */, 134 | EBCD67331C843E8600146FDB /* ToolBarNumberView.swift */, 135 | EB15452D1C856C920055715B /* ToolBarHighQualityButton.swift */, 136 | EBD4A7621D1927E1003280BF /* VideoIndicatorView.swift */, 137 | AC2A494D21AAF45F00F1CF46 /* CircleProgressView.swift */, 138 | ); 139 | name = Views; 140 | sourceTree = ""; 141 | }; 142 | EBA70B0E1C7EDA6E0089ECD7 /* Extensions */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | EBD4A75E1D192595003280BF /* UIView+Frame.swift */, 146 | EBA70B0F1C7EDB3F0089ECD7 /* PhotoPickerProtocol.swift */, 147 | EB48DC2F1C7F0EB3009F5898 /* CommonExtension.swift */, 148 | ); 149 | name = Extensions; 150 | sourceTree = ""; 151 | }; 152 | EBD4A75B1D1924A7003280BF /* IconViews */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | EBD4A75C1D1924C7003280BF /* VideoIconView.swift */, 156 | EBD4A7601D1926C9003280BF /* SlomoIconView.swift */, 157 | ); 158 | name = IconViews; 159 | sourceTree = ""; 160 | }; 161 | /* End PBXGroup section */ 162 | 163 | /* Begin PBXHeadersBuildPhase section */ 164 | EBA70AD31C7EA9320089ECD7 /* Headers */ = { 165 | isa = PBXHeadersBuildPhase; 166 | buildActionMask = 2147483647; 167 | files = ( 168 | EBA70ADA1C7EA9320089ECD7 /* PhotoPicker.h in Headers */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXHeadersBuildPhase section */ 173 | 174 | /* Begin PBXNativeTarget section */ 175 | EBA70AD51C7EA9320089ECD7 /* PhotoPicker */ = { 176 | isa = PBXNativeTarget; 177 | buildConfigurationList = EBA70ADE1C7EA9320089ECD7 /* Build configuration list for PBXNativeTarget "PhotoPicker" */; 178 | buildPhases = ( 179 | EBA70AD11C7EA9320089ECD7 /* Sources */, 180 | EBA70AD21C7EA9320089ECD7 /* Frameworks */, 181 | EBA70AD31C7EA9320089ECD7 /* Headers */, 182 | EBA70AD41C7EA9320089ECD7 /* Resources */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | ); 188 | name = PhotoPicker; 189 | productName = PhotoPicker; 190 | productReference = EBA70AD61C7EA9320089ECD7 /* PhotoPicker.framework */; 191 | productType = "com.apple.product-type.framework"; 192 | }; 193 | /* End PBXNativeTarget section */ 194 | 195 | /* Begin PBXProject section */ 196 | EBA70ACD1C7EA9320089ECD7 /* Project object */ = { 197 | isa = PBXProject; 198 | attributes = { 199 | LastUpgradeCheck = 1020; 200 | ORGANIZATIONNAME = StormXX; 201 | TargetAttributes = { 202 | EBA70AD51C7EA9320089ECD7 = { 203 | CreatedOnToolsVersion = 7.2.1; 204 | LastSwiftMigration = 0900; 205 | }; 206 | }; 207 | }; 208 | buildConfigurationList = EBA70AD01C7EA9320089ECD7 /* Build configuration list for PBXProject "PhotoPicker" */; 209 | compatibilityVersion = "Xcode 3.2"; 210 | developmentRegion = en; 211 | hasScannedForEncodings = 0; 212 | knownRegions = ( 213 | en, 214 | Base, 215 | ); 216 | mainGroup = EBA70ACC1C7EA9320089ECD7; 217 | productRefGroup = EBA70AD71C7EA9320089ECD7 /* Products */; 218 | projectDirPath = ""; 219 | projectRoot = ""; 220 | targets = ( 221 | EBA70AD51C7EA9320089ECD7 /* PhotoPicker */, 222 | ); 223 | }; 224 | /* End PBXProject section */ 225 | 226 | /* Begin PBXResourcesBuildPhase section */ 227 | EBA70AD41C7EA9320089ECD7 /* Resources */ = { 228 | isa = PBXResourcesBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | EBA70B071C7ED26F0089ECD7 /* PhotoPicker.storyboard in Resources */, 232 | EBA70B141C7F03580089ECD7 /* Images.xcassets in Resources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXResourcesBuildPhase section */ 237 | 238 | /* Begin PBXSourcesBuildPhase section */ 239 | EBA70AD11C7EA9320089ECD7 /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | EBCD67341C843E8600146FDB /* ToolBarNumberView.swift in Sources */, 244 | EB3E4A611C8031D800175777 /* AssetCell.swift in Sources */, 245 | EBD4A7631D1927E1003280BF /* VideoIndicatorView.swift in Sources */, 246 | EBA70B0D1C7ED91E0089ECD7 /* PhotoPickerController.swift in Sources */, 247 | A100447E1ECDAE640050A8F0 /* PhotoPickerThemeManager.swift in Sources */, 248 | AC2A494E21AAF46000F1CF46 /* CircleProgressView.swift in Sources */, 249 | EBA70B091C7ED6D60089ECD7 /* AlbumCell.swift in Sources */, 250 | EBA70B101C7EDB3F0089ECD7 /* PhotoPickerProtocol.swift in Sources */, 251 | EB15452E1C856C920055715B /* ToolBarHighQualityButton.swift in Sources */, 252 | EBA70B0B1C7ED8030089ECD7 /* constant.swift in Sources */, 253 | EB48DC301C7F0EB3009F5898 /* CommonExtension.swift in Sources */, 254 | EBD4A75F1D192595003280BF /* UIView+Frame.swift in Sources */, 255 | EBA70B121C7EE1580089ECD7 /* AlbumsViewController.swift in Sources */, 256 | EBD4A75D1D1924C7003280BF /* VideoIconView.swift in Sources */, 257 | EB3E4A651C8036E200175777 /* AssetsViewController.swift in Sources */, 258 | EBD4A7611D1926C9003280BF /* SlomoIconView.swift in Sources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXSourcesBuildPhase section */ 263 | 264 | /* Begin XCBuildConfiguration section */ 265 | EBA70ADC1C7EA9320089ECD7 /* Debug */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ALWAYS_SEARCH_USER_PATHS = NO; 269 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 271 | CLANG_CXX_LIBRARY = "libc++"; 272 | CLANG_ENABLE_MODULES = YES; 273 | CLANG_ENABLE_OBJC_ARC = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_EMPTY_BODY = YES; 281 | CLANG_WARN_ENUM_CONVERSION = YES; 282 | CLANG_WARN_INFINITE_RECURSION = YES; 283 | CLANG_WARN_INT_CONVERSION = YES; 284 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 285 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 286 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 288 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 289 | CLANG_WARN_STRICT_PROTOTYPES = YES; 290 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 291 | CLANG_WARN_UNREACHABLE_CODE = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 294 | COPY_PHASE_STRIP = NO; 295 | CURRENT_PROJECT_VERSION = 1; 296 | DEBUG_INFORMATION_FORMAT = dwarf; 297 | ENABLE_STRICT_OBJC_MSGSEND = YES; 298 | ENABLE_TESTABILITY = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu99; 300 | GCC_DYNAMIC_NO_PIC = NO; 301 | GCC_NO_COMMON_BLOCKS = YES; 302 | GCC_OPTIMIZATION_LEVEL = 0; 303 | GCC_PREPROCESSOR_DEFINITIONS = ( 304 | "DEBUG=1", 305 | "$(inherited)", 306 | ); 307 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 308 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 309 | GCC_WARN_UNDECLARED_SELECTOR = YES; 310 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 311 | GCC_WARN_UNUSED_FUNCTION = YES; 312 | GCC_WARN_UNUSED_VARIABLE = YES; 313 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 314 | MTL_ENABLE_DEBUG_INFO = YES; 315 | ONLY_ACTIVE_ARCH = YES; 316 | SDKROOT = iphoneos; 317 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 318 | TARGETED_DEVICE_FAMILY = "1,2"; 319 | VERSIONING_SYSTEM = "apple-generic"; 320 | VERSION_INFO_PREFIX = ""; 321 | }; 322 | name = Debug; 323 | }; 324 | EBA70ADD1C7EA9320089ECD7 /* Release */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 329 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 330 | CLANG_CXX_LIBRARY = "libc++"; 331 | CLANG_ENABLE_MODULES = YES; 332 | CLANG_ENABLE_OBJC_ARC = YES; 333 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 334 | CLANG_WARN_BOOL_CONVERSION = YES; 335 | CLANG_WARN_COMMA = YES; 336 | CLANG_WARN_CONSTANT_CONVERSION = YES; 337 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 338 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INFINITE_RECURSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 345 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 347 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 348 | CLANG_WARN_STRICT_PROTOTYPES = YES; 349 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 350 | CLANG_WARN_UNREACHABLE_CODE = YES; 351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 352 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 353 | COPY_PHASE_STRIP = NO; 354 | CURRENT_PROJECT_VERSION = 1; 355 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 356 | ENABLE_NS_ASSERTIONS = NO; 357 | ENABLE_STRICT_OBJC_MSGSEND = YES; 358 | GCC_C_LANGUAGE_STANDARD = gnu99; 359 | GCC_NO_COMMON_BLOCKS = YES; 360 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 361 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 362 | GCC_WARN_UNDECLARED_SELECTOR = YES; 363 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 364 | GCC_WARN_UNUSED_FUNCTION = YES; 365 | GCC_WARN_UNUSED_VARIABLE = YES; 366 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 367 | MTL_ENABLE_DEBUG_INFO = NO; 368 | SDKROOT = iphoneos; 369 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 370 | TARGETED_DEVICE_FAMILY = "1,2"; 371 | VALIDATE_PRODUCT = YES; 372 | VERSIONING_SYSTEM = "apple-generic"; 373 | VERSION_INFO_PREFIX = ""; 374 | }; 375 | name = Release; 376 | }; 377 | EBA70ADF1C7EA9320089ECD7 /* Debug */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | CLANG_ENABLE_MODULES = YES; 381 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 382 | DEFINES_MODULE = YES; 383 | DYLIB_COMPATIBILITY_VERSION = 1; 384 | DYLIB_CURRENT_VERSION = 1; 385 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 386 | FRAMEWORK_SEARCH_PATHS = ( 387 | "$(inherited)", 388 | "$(PROJECT_DIR)/Carthage/Build/iOS", 389 | ); 390 | INFOPLIST_FILE = PhotoPicker/Info.plist; 391 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 392 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 393 | PRODUCT_BUNDLE_IDENTIFIER = com.StormXX.PhotoPicker; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | SKIP_INSTALL = YES; 396 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 397 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 398 | SWIFT_VERSION = 5.0; 399 | }; 400 | name = Debug; 401 | }; 402 | EBA70AE01C7EA9320089ECD7 /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | CLANG_ENABLE_MODULES = YES; 406 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 407 | DEFINES_MODULE = YES; 408 | DYLIB_COMPATIBILITY_VERSION = 1; 409 | DYLIB_CURRENT_VERSION = 1; 410 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 411 | FRAMEWORK_SEARCH_PATHS = ( 412 | "$(inherited)", 413 | "$(PROJECT_DIR)/Carthage/Build/iOS", 414 | ); 415 | INFOPLIST_FILE = PhotoPicker/Info.plist; 416 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = com.StormXX.PhotoPicker; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SKIP_INSTALL = YES; 421 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 422 | SWIFT_VERSION = 5.0; 423 | }; 424 | name = Release; 425 | }; 426 | /* End XCBuildConfiguration section */ 427 | 428 | /* Begin XCConfigurationList section */ 429 | EBA70AD01C7EA9320089ECD7 /* Build configuration list for PBXProject "PhotoPicker" */ = { 430 | isa = XCConfigurationList; 431 | buildConfigurations = ( 432 | EBA70ADC1C7EA9320089ECD7 /* Debug */, 433 | EBA70ADD1C7EA9320089ECD7 /* Release */, 434 | ); 435 | defaultConfigurationIsVisible = 0; 436 | defaultConfigurationName = Release; 437 | }; 438 | EBA70ADE1C7EA9320089ECD7 /* Build configuration list for PBXNativeTarget "PhotoPicker" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | EBA70ADF1C7EA9320089ECD7 /* Debug */, 442 | EBA70AE01C7EA9320089ECD7 /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | /* End XCConfigurationList section */ 448 | }; 449 | rootObject = EBA70ACD1C7EA9320089ECD7 /* Project object */; 450 | } 451 | -------------------------------------------------------------------------------- /PhotoPicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PhotoPicker.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PhotoPicker.xcodeproj/project.xcworkspace/xcuserdata/StormXX.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/PhotoPicker.xcodeproj/project.xcworkspace/xcuserdata/StormXX.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PhotoPicker.xcodeproj/xcshareddata/xcschemes/PhotoPicker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /PhotoPicker.xcodeproj/xcuserdata/StormXX.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PhotoPicker.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | EBA70AD51C7EA9320089ECD7 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /PhotoPicker.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PhotoPicker.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PhotoPicker.xcworkspace/xcuserdata/StormXX.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/PhotoPicker.xcworkspace/xcuserdata/StormXX.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PhotoPicker.xcworkspace/xcuserdata/StormXX.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /PhotoPicker/AlbumCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumCell.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlbumCell: UITableViewCell { 12 | 13 | @IBOutlet weak var albumCover1: UIImageView! 14 | @IBOutlet weak var albumCover2: UIImageView! 15 | @IBOutlet weak var albumCover3: UIImageView! 16 | @IBOutlet weak var titleLabel: UILabel! 17 | @IBOutlet weak var countLabel: UILabel! 18 | 19 | var borderWidth: CGFloat = 0.0 { 20 | didSet { 21 | albumCover1.layer.borderColor = AlbumCoverBorderColor 22 | albumCover2.layer.borderColor = AlbumCoverBorderColor 23 | albumCover3.layer.borderColor = AlbumCoverBorderColor 24 | 25 | albumCover1.layer.borderWidth = borderWidth 26 | albumCover2.layer.borderWidth = borderWidth 27 | albumCover3.layer.borderWidth = borderWidth 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PhotoPicker/AlbumsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumsViewController.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class AlbumsViewController: UITableViewController { 13 | 14 | //MARK: - public property 15 | weak var photoPickerController: PhotoPickerController! 16 | var highQualityImageByDefault: Bool = false 17 | 18 | //MARK: - private property 19 | fileprivate var fetchedResults: [PHFetchResult] = [] 20 | fileprivate var assetCollections: [PHAssetCollection] = [] 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | setupCancelButton() 26 | tableView.rowHeight = 86.0 27 | loadAlbums() 28 | PHPhotoLibrary.shared().register(self) 29 | } 30 | 31 | deinit { 32 | PHPhotoLibrary.shared().unregisterChangeObserver(self) 33 | } 34 | 35 | override func viewWillAppear(_ animated: Bool) { 36 | super.viewWillAppear(animated) 37 | 38 | navigationItem.title = localizedString["PhotoPicker.Title"] 39 | navigationItem.prompt = photoPickerController.prompt 40 | 41 | navigationController?.setToolbarHidden(true, animated: false) 42 | } 43 | 44 | // MARK: - Table view data source 45 | 46 | override func numberOfSections(in tableView: UITableView) -> Int { 47 | return 1 48 | } 49 | 50 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 51 | return assetCollections.count 52 | } 53 | 54 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 55 | let cell = tableView.dequeueReusableCell(withIdentifier: albumCellIdentifier, for: indexPath) as! AlbumCell 56 | fillCell(cell, forIndexPath: indexPath) 57 | return cell 58 | } 59 | 60 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 61 | tableView.deselectRow(at: indexPath, animated: true) 62 | } 63 | 64 | // MARK: - Navigation 65 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 66 | if let assetsViewController = segue.destination as? AssetsViewController { 67 | assetsViewController.photoPickerController = photoPickerController 68 | assetsViewController.assetCollection = assetCollections[tableView.indexPathForSelectedRow!.row] 69 | assetsViewController.highQualityImageByDefault = highQualityImageByDefault 70 | } 71 | } 72 | } 73 | 74 | //MARK: - response method 75 | extension AlbumsViewController { 76 | @objc func cancelButtonTapped(_ sender: UIBarButtonItem) { 77 | photoPickerController.delegate?.photoPickerControllerDidCancel(photoPickerController) 78 | } 79 | } 80 | 81 | //MARK: - help method 82 | extension AlbumsViewController { 83 | 84 | func setupCancelButton() { 85 | let cancel = UIBarButtonItem(title: localizedString["PhotoPicker.Cancel"], style: .plain, target: self, action: #selector(cancelButtonTapped)) 86 | navigationItem.rightBarButtonItem = cancel 87 | } 88 | 89 | func loadAlbums() { 90 | let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil) 91 | let userAlbums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: nil) 92 | fetchedResults = [smartAlbums, userAlbums] 93 | 94 | processAlbums() 95 | } 96 | 97 | func processAlbums() { 98 | assetCollections.removeAll() 99 | 100 | guard let assetCollectionsSubTypes = self.photoPickerController.assetCollectionSubtypes else { return } 101 | 102 | let smartAlbums = fetchedResults[0] 103 | let userAlbums = fetchedResults[1] 104 | smartAlbums.enumerateObjects(options: .concurrent) { [weak self] (assetCollection, index, stop) in 105 | if assetCollectionsSubTypes.contains(assetCollection.assetCollectionSubtype) { 106 | self?.assetCollections.append(assetCollection) 107 | } 108 | } 109 | 110 | userAlbums.enumerateObjects(options: .concurrent) { [weak self] (assetCollection, index, stop) in 111 | if assetCollectionsSubTypes.contains(assetCollection.assetCollectionSubtype) { 112 | self?.assetCollections.append(assetCollection) 113 | } 114 | } 115 | } 116 | 117 | func fillCell(_ cell: AlbumCell, forIndexPath indexPath: IndexPath) { 118 | cell.tag = indexPath.row 119 | cell.borderWidth = 1.0 / traitCollection.displayScale 120 | 121 | let assetCollection = assetCollections[indexPath.row] 122 | 123 | let fetchOptions = PHFetchOptions() 124 | fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)] 125 | switch photoPickerController.mediaType { 126 | case .image: 127 | fetchOptions.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.image.rawValue) 128 | case .video: 129 | fetchOptions.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.video.rawValue) 130 | default: 131 | break 132 | } 133 | 134 | let fetchResult = PHAsset.fetchAssets(in: assetCollection, options: fetchOptions) 135 | let imageManager = PHImageManager.default() 136 | 137 | if fetchResult.count >= 3 { 138 | cell.albumCover3.isHidden = false 139 | imageManager.requestImage(for: fetchResult[fetchResult.count - 3], 140 | targetSize: cell.albumCover3.frame.size.scale(traitCollection.displayScale), 141 | contentMode: .aspectFill, 142 | options: nil, 143 | resultHandler: { (image, info) -> Void in 144 | guard let image = image, cell.tag == indexPath.row else { return } 145 | cell.albumCover3.image = image 146 | }) 147 | } else { 148 | cell.albumCover3.isHidden = true 149 | } 150 | 151 | if fetchResult.count >= 2 { 152 | cell.albumCover2.isHidden = false 153 | imageManager.requestImage(for: fetchResult[fetchResult.count - 2], 154 | targetSize: cell.albumCover2.frame.size.scale(traitCollection.displayScale), 155 | contentMode: .aspectFill, 156 | options: nil, 157 | resultHandler: { (image, info) -> Void in 158 | guard let image = image, cell.tag == indexPath.row else { return } 159 | cell.albumCover2.image = image 160 | }) 161 | } else { 162 | cell.albumCover2.isHidden = true 163 | } 164 | 165 | if fetchResult.count >= 1 { 166 | imageManager.requestImage(for: fetchResult[fetchResult.count - 1], 167 | targetSize: cell.albumCover1.frame.size.scale(traitCollection.displayScale), 168 | contentMode: .aspectFill, 169 | options: nil, 170 | resultHandler: { (image, info) -> Void in 171 | guard let image = image, cell.tag == indexPath.row else { return } 172 | cell.albumCover1.image = image 173 | }) 174 | } 175 | 176 | if fetchResult.count == 0 { 177 | cell.albumCover3.isHidden = false 178 | cell.albumCover2.isHidden = false 179 | 180 | let placeholderImage = UIImage(named: "EmptyPlaceholder", in: currentBundle, compatibleWith: nil) 181 | cell.albumCover1.image = placeholderImage 182 | cell.albumCover2.image = placeholderImage 183 | cell.albumCover3.image = placeholderImage 184 | } 185 | 186 | cell.titleLabel.text = assetCollection.localizedTitle 187 | cell.countLabel.text = "\(fetchResult.count)" 188 | } 189 | } 190 | 191 | extension AlbumsViewController: PHPhotoLibraryChangeObserver { 192 | func photoLibraryDidChange(_ changeInstance: PHChange) { 193 | DispatchQueue.main.async { [weak self]() -> Void in 194 | guard let strongSelf = self else { return } 195 | var changedFetchResult = strongSelf.fetchedResults 196 | 197 | for (index, fetchResult) in strongSelf.fetchedResults.enumerated() { 198 | guard let changeDetails = changeInstance.changeDetails(for: fetchResult) else { continue } 199 | changedFetchResult[index] = changeDetails.fetchResultAfterChanges 200 | } 201 | 202 | if strongSelf.fetchedResults != changedFetchResult { 203 | strongSelf.fetchedResults = changedFetchResult 204 | 205 | strongSelf.processAlbums() 206 | strongSelf.tableView.reloadData() 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /PhotoPicker/AssetCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetCell.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/26. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AssetCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var imageView: UIImageView! 14 | @IBOutlet weak var overlayView: UIView! 15 | @IBOutlet weak var checkMarkImageView: UIImageView! 16 | @IBOutlet weak var tapGestureView: UIView! 17 | @IBOutlet weak var disableView: UIView! 18 | @IBOutlet weak var videoIndicatorView: VideoIndicatorView! 19 | fileprivate var checkHandler: ((_ checked: Bool) -> Bool)? 20 | 21 | lazy var progressView: CircularProgressView = { 22 | let progressView = CircularProgressView(frame: .zero) 23 | progressView.setCircleStrokeWidth(5) 24 | progressView.translatesAutoresizingMaskIntoConstraints = false 25 | return progressView 26 | }() 27 | 28 | var checked: Bool = false 29 | 30 | var isDisabled: Bool = false { 31 | didSet { 32 | disableView.isHidden = !isDisabled 33 | } 34 | } 35 | 36 | override func awakeFromNib() { 37 | super.awakeFromNib() 38 | imageView.backgroundColor = UIColor(white: 0.95, alpha: 1.0) 39 | checkMarkImageView.tintColor = themeToolBarTintColor 40 | 41 | addSubview(progressView) 42 | progressView.leftAnchor.constraint(equalTo: checkMarkImageView.leftAnchor).isActive = true 43 | progressView.rightAnchor.constraint(equalTo: checkMarkImageView.rightAnchor).isActive = true 44 | progressView.topAnchor.constraint(equalTo: checkMarkImageView.topAnchor).isActive = true 45 | progressView.bottomAnchor.constraint(equalTo: checkMarkImageView.bottomAnchor).isActive = true 46 | 47 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapGestureHandler(_:))) 48 | tapGestureView.isUserInteractionEnabled = true 49 | tapGestureView.addGestureRecognizer(tapGesture) 50 | tapGestureView.isExclusiveTouch = true 51 | } 52 | 53 | override func prepareForReuse() { 54 | super.prepareForReuse() 55 | checkHandler = nil 56 | imageView.image = nil 57 | progressView.isHidden = true 58 | } 59 | 60 | @objc func tapGestureHandler(_ recognizer: UIGestureRecognizer) { 61 | guard let handler = checkHandler else { return } 62 | if handler(checked) { 63 | setChecked(!checked, animation: true) 64 | } 65 | } 66 | 67 | func addCheckHandler(_ handler: @escaping (_ checked: Bool) -> Bool) { 68 | checkHandler = handler 69 | } 70 | 71 | func setChecked(_ checked: Bool, animation: Bool) { 72 | checkMarkImageView.image = checked ? UIImage(named: selectedCheckMarkImageName, in: currentBundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) : UIImage(named: unselectedCheckMarkImageName, in: currentBundle, compatibleWith: nil) 73 | if !self.checked && checked && animation { 74 | UIView.animateKeyframes(withDuration: 0.33, delay: 0.0, options: [], animations: { () -> Void in 75 | UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: { [unowned self]() -> Void in 76 | let transform = CGAffineTransform(scaleX: 1.3, y: 1.3) 77 | self.checkMarkImageView.transform = transform 78 | }) 79 | 80 | UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { [unowned self]() -> Void in 81 | self.checkMarkImageView.transform = CGAffineTransform.identity 82 | }) 83 | }, completion: nil) 84 | } 85 | self.checked = checked 86 | } 87 | 88 | func showVideoIcon() { 89 | videoIndicatorView.videoIcon.isHidden = false 90 | videoIndicatorView.slomoIcon.isHidden = true 91 | } 92 | 93 | func showSlomoIcon() { 94 | videoIndicatorView.videoIcon.isHidden = true 95 | videoIndicatorView.slomoIcon.isHidden = false 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /PhotoPicker/AssetsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsViewController.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/26. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | import PhotoBrowser 12 | import Foundation 13 | 14 | class AssetsViewController: UICollectionViewController { 15 | 16 | //MARK: - public property 17 | weak var photoPickerController: PhotoPickerController! 18 | var highQualityImageByDefault: Bool = false 19 | 20 | var selectedAssets: [PHAsset] = [] { 21 | didSet { 22 | updateHighQualityImageSize() 23 | } 24 | } 25 | 26 | var assetCollection: PHAssetCollection! { 27 | didSet { 28 | self.updateFetchRequest() 29 | } 30 | } 31 | 32 | //MARK: - private property 33 | fileprivate let imageManager: PHCachingImageManager = PHCachingImageManager() 34 | fileprivate var previousPreheatRect: CGRect = CGRect.zero 35 | fileprivate var assetsFetchResults: PHFetchResult! 36 | fileprivate var lastSelectItemIndexPath: IndexPath? 37 | fileprivate var toolbarNumberView: ToolBarNumberView! 38 | fileprivate var toolbarHighQualityButton: ToolBarHighQualityButton! 39 | fileprivate var sendBarItem: UIBarButtonItem! 40 | fileprivate var selectedIndexPaths: [IndexPath] = [] 41 | fileprivate var isFirstLoading: Bool = true 42 | fileprivate var canSelectVideo: Bool = true 43 | fileprivate var canSelectImage: Bool = true 44 | fileprivate weak var photoBrowserHighQualityButton: ToolBarHighQualityButton? 45 | 46 | private lazy var thumbnailRequestOptions: PHImageRequestOptions = { 47 | let requestOptions = PHImageRequestOptions() 48 | requestOptions.resizeMode = .fast 49 | requestOptions.deliveryMode = .highQualityFormat 50 | requestOptions.isNetworkAccessAllowed = true 51 | return requestOptions 52 | }() 53 | 54 | private var totalDownloadAssetCount = 0 55 | 56 | override func viewDidLoad() { 57 | super.viewDidLoad() 58 | 59 | collectionView?.layoutIfNeeded() 60 | if #available(iOS 10.0, *) { 61 | collectionView?.isPrefetchingEnabled = false 62 | } 63 | view.layoutIfNeeded() 64 | setupCancelButton() 65 | setupToolBar() 66 | resetCachedAssets() 67 | PHPhotoLibrary.shared().register(self) 68 | } 69 | 70 | deinit { 71 | PHPhotoLibrary.shared().unregisterChangeObserver(self) 72 | } 73 | 74 | override func viewWillAppear(_ animated: Bool) { 75 | super.viewWillAppear(animated) 76 | 77 | navigationItem.title = assetCollection.localizedTitle 78 | navigationItem.prompt = photoPickerController.prompt 79 | 80 | collectionView?.allowsMultipleSelection = photoPickerController.allowMultipleSelection 81 | collectionView?.reloadData() 82 | 83 | if assetsFetchResults.count > 0 && isMovingToParent { 84 | let indexPath = IndexPath(item: assetsFetchResults.count - 1, section: 0) 85 | collectionView?.layoutIfNeeded() 86 | collectionView?.scrollToItem(at: indexPath, at: .top, animated: false) 87 | } 88 | } 89 | 90 | override func viewDidAppear(_ animated: Bool) { 91 | super.viewDidAppear(animated) 92 | 93 | updateCachedAssets() 94 | } 95 | 96 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 97 | guard let collectionView = collectionView else { return } 98 | collectionViewLayout.invalidateLayout() 99 | if let indexPath = collectionView.indexPathsForVisibleItems.last { 100 | coordinator.animate(alongsideTransition: nil, completion: { (context) -> Void in 101 | collectionView.scrollToItem(at: indexPath, at: .bottom, animated: false) 102 | }) 103 | } 104 | } 105 | 106 | // MARK: UICollectionViewDataSource 107 | override func numberOfSections(in collectionView: UICollectionView) -> Int { 108 | return 1 109 | } 110 | 111 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 112 | return assetsFetchResults.count 113 | } 114 | 115 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 116 | let cell: AssetCell = collectionView.dequeueReusableCell(withReuseIdentifier: assetCellIdentifier, for: indexPath) as! AssetCell 117 | fillCell(cell, forIndexPath: indexPath) 118 | cell.addCheckHandler { [weak self, weak cell] (checked) -> Bool in 119 | guard let self = self, let cell = cell, self.shouldSelectItemAtIndexPath(indexPath, checked: checked) else { 120 | return false 121 | } 122 | if !checked { 123 | self.selectItemAtIndexPath(indexPath) 124 | } else { 125 | self.deselectItemAtIndexPath(indexPath, uncheckCell: false) 126 | } 127 | self.downloadOriginImage(for: cell, indexPath: indexPath, completion: {}) 128 | return true 129 | } 130 | return cell 131 | } 132 | 133 | override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 134 | if kind == UICollectionView.elementKindSectionFooter { 135 | let footerView: UICollectionReusableView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: assetFooterViewIdentifier, for: indexPath) 136 | let numberOfPhotos = assetsFetchResults.countOfAssets(with: .image) 137 | let numberOfVideos = assetsFetchResults.countOfAssets(with: .video) 138 | 139 | if let label = footerView.viewWithTag(100) as? UILabel { 140 | label.text = "\(numberOfPhotos) \(localizedString["PhotoPicker.Photos"]!) , \(numberOfVideos) \(localizedString["PhotoPicker.Videos"]!)" 141 | } 142 | return footerView 143 | } 144 | return UICollectionReusableView() 145 | } 146 | 147 | // MARK: UICollectionViewDelegate 148 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 149 | guard let cell = collectionView.cellForItem(at: indexPath) as? AssetCell, cell.isDisabled == false else { 150 | return 151 | } 152 | 153 | downloadOriginImage(for: cell, indexPath: indexPath) { [weak self] in 154 | self?.showPhotoBrowser(for: indexPath) 155 | } 156 | } 157 | 158 | private func downloadOriginImage(for cell: AssetCell, indexPath: IndexPath, completion: @escaping () -> Void) { 159 | totalDownloadAssetCount += 1 160 | let selectedAsset = assetsFetchResults[indexPath.item] 161 | let options = PHImageRequestOptions() 162 | options.isNetworkAccessAllowed = true 163 | options.deliveryMode = .highQualityFormat 164 | options.progressHandler = { (progress: Double, error, stop, info) in 165 | print("progress: \(progress)") 166 | DispatchQueue.main.async { [weak self, weak cell] in 167 | guard let strongSelf = self, let strongCell = cell else { 168 | return 169 | } 170 | strongCell.checkMarkImageView.isHidden = true 171 | strongSelf.sendBarItem?.isEnabled = false 172 | strongCell.progressView.progress = CGFloat(progress) 173 | } 174 | } 175 | 176 | imageManager.requestImage(for: selectedAsset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: options) { [weak self, weak cell] (image, info) in 177 | guard let strongSelf = self, let strongCell = cell else { 178 | return 179 | } 180 | strongSelf.totalDownloadAssetCount -= 1 181 | strongCell.checkMarkImageView.isHidden = false 182 | strongCell.progressView.isHidden = true 183 | strongSelf.sendBarItem?.isEnabled = (!strongSelf.selectedAssets.isEmpty) && (strongSelf.totalDownloadAssetCount == 0) 184 | completion() 185 | } 186 | } 187 | 188 | private func showPhotoBrowser(for indexPath: IndexPath) { 189 | if photoPickerController.allowMultipleSelection { 190 | let photoBrowser = PhotoBrowser() 191 | photoBrowser.currentIndex = indexPath.item 192 | // set isFrom PhotoPicker 193 | photoBrowser.isFromPhotoPicker = true 194 | photoBrowser.isShowMoreButton = false 195 | photoBrowser.selectedIndex = selectedIndexPaths.map { return $0.item } 196 | // action 197 | let highQualityButton = ToolBarHighQualityButton(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 150, height: 21.0))) 198 | highQualityButton.assetsViewController = self 199 | highQualityButton.checked = self.toolbarHighQualityButton.checked 200 | if highQualityButton.checked { 201 | highQualityButton.highqualityImageSize = self.getImageSize(at: photoBrowser.currentIndex) 202 | } 203 | highQualityButton.action = { [weak self, weak highQualityButton, weak photoBrowser] (checked) in 204 | guard let self = self, let highQualityButton = highQualityButton, let photoBrowser = photoBrowser else { 205 | return 206 | } 207 | self.toolbarHighQualityButton.checked = checked 208 | if checked { 209 | highQualityButton.highqualityImageSize = self.getImageSize(at: photoBrowser.currentIndex) 210 | } 211 | } 212 | photoBrowserHighQualityButton = highQualityButton 213 | let barItem = UIBarButtonItem(customView: highQualityButton) 214 | let actionItem = PBActionBarItem(barButtonItem: barItem) 215 | let middleEmptyItem = PBActionBarItem(title: "", style: .plain) 216 | let rightEmptyItem = PBActionBarItem(title: "", style: .plain) 217 | photoBrowser.actionItems = [actionItem, middleEmptyItem, rightEmptyItem] 218 | 219 | // configuration 220 | let indexSet = IndexSet(integersIn: 0.. 0 else { return } 238 | photoPickerController.delegate?.photoPickerController(photoPickerController, didFinishPickingAssets: selectedAssets, needHighQualityImage: toolbarHighQualityButton.checked) 239 | } 240 | 241 | @objc func cancelButtonTapped(_ sender: UIBarButtonItem) { 242 | photoPickerController.delegate?.photoPickerControllerDidCancel(photoPickerController) 243 | } 244 | 245 | func photoBrowserOriginButtonTapped() {} 246 | } 247 | 248 | //MARK: - UI help method 249 | extension AssetsViewController { 250 | func setupCancelButton() { 251 | let cancel = UIBarButtonItem(title: localizedString["PhotoPicker.Cancel"], style: .plain, target: self, action: #selector(cancelButtonTapped)) 252 | navigationItem.rightBarButtonItem = cancel 253 | } 254 | 255 | func setupToolBar() { 256 | guard photoPickerController.allowMultipleSelection else { return } 257 | toolbarNumberView = ToolBarNumberView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 21.0, height: 21.0))) 258 | toolbarHighQualityButton = ToolBarHighQualityButton(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 150, height: 21.0))) 259 | toolbarHighQualityButton.assetsViewController = self 260 | if highQualityImageByDefault { 261 | toolbarHighQualityButton.checked = true 262 | } 263 | 264 | sendBarItem = UIBarButtonItem(title: localizedString["PhotoPicker.Send"], style: .plain, target: self, action: #selector(AssetsViewController.sendButtonTapped)) 265 | sendBarItem.isEnabled = false 266 | let highqualityBarItem = UIBarButtonItem(customView: toolbarHighQualityButton) 267 | let numberBarItem = UIBarButtonItem(customView: toolbarNumberView) 268 | let leftSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 269 | toolbarItems = [highqualityBarItem, leftSpace, numberBarItem, sendBarItem] 270 | navigationController?.toolbar.tintColor = themeToolBarTintColor 271 | navigationController?.setToolbarHidden(false, animated: true) 272 | } 273 | 274 | func toggleHighQualityButtonHidden(_ hidden: Bool) { 275 | toolbarHighQualityButton.isHidden = hidden 276 | } 277 | 278 | func updateToolBar() { 279 | guard photoPickerController.allowMultipleSelection else { return } 280 | sendBarItem?.isEnabled = (selectedAssets.count != 0) 281 | toolbarNumberView.number = selectedAssets.count 282 | } 283 | 284 | func updateHighQualityImageSize() { 285 | guard toolbarHighQualityButton.checked else { return } 286 | var size: Int = 0 287 | selectedAssets.forEach { (asset) -> () in 288 | let options = PHImageRequestOptions() 289 | options.isSynchronous = true 290 | PHImageManager.default().requestImageData(for: asset, options: options, resultHandler: { (imageData, dataUTI, orientation, info) -> Void in 291 | if let data = imageData { 292 | size += data.count 293 | } 294 | }) 295 | } 296 | self.toolbarHighQualityButton.highqualityImageSize = size 297 | } 298 | 299 | func getImageSize(at index: Int) -> Int { 300 | var size: Int = 0 301 | let asset = assetsFetchResults[index] 302 | let options = PHImageRequestOptions() 303 | options.isSynchronous = true 304 | PHImageManager.default().requestImageData(for: asset, options: options, resultHandler: { (imageData, dataUTI, orientation, info) -> Void in 305 | if let data = imageData { 306 | size += data.count 307 | } 308 | }) 309 | return size 310 | } 311 | 312 | func isVideoAsset(_ indexPath: IndexPath?) -> Bool { 313 | guard let `indexPath` = indexPath else { return false } 314 | return assetsFetchResults[indexPath.item].mediaType == .video 315 | } 316 | } 317 | 318 | //MARK: - collection view help method 319 | extension AssetsViewController { 320 | func fillCell(_ cell: AssetCell, forIndexPath indexPath: IndexPath) { 321 | cell.tag = indexPath.item 322 | 323 | let asset = assetsFetchResults[indexPath.item] 324 | let itemSize = (collectionViewLayout as! UICollectionViewFlowLayout).itemSize 325 | let targetSize = itemSize.scale(traitCollection.displayScale) 326 | cell.overlayView.isHidden = !photoPickerController.allowMultipleSelection 327 | 328 | imageManager.requestImage(for: asset, 329 | targetSize: targetSize, 330 | contentMode: .aspectFill, 331 | options: thumbnailRequestOptions) { (image, info) -> Void in 332 | guard let image = image , cell.tag == indexPath.item else { 333 | return 334 | } 335 | cell.imageView.image = image 336 | } 337 | 338 | cell.setChecked(selectedIndexPaths.contains(indexPath), animation: false) 339 | 340 | if asset.mediaType == .video { 341 | cell.disableView.isHidden = cell.checked || canSelectVideo 342 | cell.videoIndicatorView.isHidden = false 343 | let minutes = Int(asset.duration / 60.0) 344 | let seconds = Int(ceil(asset.duration - 60.0 * Double(minutes))) 345 | cell.videoIndicatorView.timeLabel.text = String(format: "%02ld:%02ld", minutes, seconds) 346 | 347 | if asset.mediaSubtypes == .videoHighFrameRate { 348 | cell.showSlomoIcon() 349 | } else { 350 | cell.showVideoIcon() 351 | } 352 | } else { 353 | cell.disableView.isHidden = cell.checked || canSelectImage 354 | cell.videoIndicatorView.isHidden = true 355 | } 356 | } 357 | 358 | func isMaximumSelectionReached() -> Bool { 359 | if photoPickerController.minimumNumberOfSelection <= photoPickerController.maximumNumberOfSelection { 360 | return photoPickerController.maximumNumberOfSelection <= selectedAssets.count 361 | } else { 362 | return false 363 | } 364 | } 365 | } 366 | 367 | //MARK: - asset help method 368 | extension AssetsViewController { 369 | 370 | func updateFetchRequest() { 371 | guard let assetCollection = assetCollection else { 372 | assetsFetchResults = nil 373 | return 374 | } 375 | 376 | let fetchOptions = PHFetchOptions() 377 | fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)] 378 | switch photoPickerController.mediaType { 379 | case .image: 380 | fetchOptions.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.image.rawValue) 381 | case .video: 382 | fetchOptions.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.video.rawValue) 383 | default: 384 | break 385 | } 386 | 387 | assetsFetchResults = PHAsset.fetchAssets(in: assetCollection, options: fetchOptions) 388 | } 389 | 390 | func resetCachedAssets() { 391 | imageManager.stopCachingImagesForAllAssets() 392 | previousPreheatRect = CGRect.zero 393 | } 394 | 395 | func updateCachedAssets() { 396 | guard isViewLoaded, let _ = view.window, let collectionView = self.collectionView else { return } 397 | 398 | // The preheat window is twice the height of the visible rect. 399 | var preheatRect = collectionView.bounds 400 | preheatRect = preheatRect.insetBy(dx: 0.0, dy: -0.5 * preheatRect.height) 401 | 402 | /* 403 | Check if the collection view is showing an area that is significantly 404 | different to the last preheated area. 405 | */ 406 | let delta = abs(preheatRect.midY - previousPreheatRect.midY) 407 | 408 | if delta > (collectionView.bounds.height / 3.0) { 409 | var addedIndexPaths: [IndexPath] = [] 410 | var removedIndexPaths: [IndexPath] = [] 411 | 412 | computeDifferenceBetweenRect(previousPreheatRect, andRect: preheatRect, 413 | removedHandler: { (removedRect) -> Void in 414 | guard let indexPaths = collectionView.pp_indexPathsForElementsInRect(removedRect) else { return } 415 | removedIndexPaths.append(contentsOf: indexPaths) 416 | }, addedHandler: { (addedRect) -> Void in 417 | guard let indexPaths = collectionView.pp_indexPathsForElementsInRect(addedRect) else { return } 418 | addedIndexPaths.append(contentsOf: indexPaths) 419 | }) 420 | 421 | let itemSize = (collectionViewLayout as! UICollectionViewFlowLayout).itemSize 422 | let targetSize = itemSize.scale(traitCollection.displayScale) 423 | 424 | if let assetsToStartCaching = assetsAtIndexPaths(addedIndexPaths) { 425 | imageManager.startCachingImages(for: assetsToStartCaching, 426 | targetSize: targetSize, 427 | contentMode: .aspectFill, 428 | options: nil) 429 | } 430 | 431 | if let assetsToStopCaching = assetsAtIndexPaths(removedIndexPaths) { 432 | imageManager.stopCachingImages(for: assetsToStopCaching, 433 | targetSize: targetSize, 434 | contentMode: .aspectFill, 435 | options: nil) 436 | } 437 | 438 | previousPreheatRect = preheatRect 439 | } 440 | 441 | } 442 | 443 | func computeDifferenceBetweenRect(_ oldRect: CGRect, andRect newRect: CGRect, removedHandler: (_ removedRect: CGRect) -> Void, addedHandler: (_ addedRect: CGRect) -> Void) { 444 | if oldRect.intersects(newRect) { 445 | let oldMaxY = oldRect.maxY 446 | let oldMinY = oldRect.minY 447 | let newMaxY = newRect.maxY 448 | let newMinY = newRect.minY 449 | 450 | if newMaxY > oldMaxY { 451 | let rectToAdd = CGRect(x: newRect.origin.x, y: oldMaxY, width: newRect.size.width, height: (newMaxY - oldMaxY)) 452 | addedHandler(rectToAdd) 453 | } 454 | 455 | if oldMinY > newMinY { 456 | let rectToAdd = CGRect(x: newRect.origin.x, y: newMinY, width: newRect.size.width, height: (oldMinY - newMinY)) 457 | addedHandler(rectToAdd) 458 | } 459 | 460 | if newMaxY < oldMaxY { 461 | let rectToRemove = CGRect(x: newRect.origin.x, y: newMaxY, width: newRect.size.width, height: (oldMaxY - newMaxY)) 462 | removedHandler(rectToRemove) 463 | } 464 | 465 | if oldMinY < newMinY { 466 | let rectToRemove = CGRect(x: newRect.origin.x, y: oldMinY, width: newRect.size.width, height: (newMinY - oldMinY)) 467 | removedHandler(rectToRemove) 468 | } 469 | } else { 470 | addedHandler(newRect) 471 | removedHandler(oldRect) 472 | } 473 | } 474 | 475 | func assetsAtIndexPaths(_ indexPaths: [IndexPath]) -> [PHAsset]? { 476 | guard indexPaths.count > 0 else { return nil } 477 | 478 | var assets: [PHAsset] = [] 479 | for indexPath in indexPaths { 480 | guard indexPath.item < assetsFetchResults.count else { break } 481 | let asset: PHAsset = assetsFetchResults[indexPath.item] 482 | assets.append(asset) 483 | } 484 | 485 | return assets 486 | } 487 | } 488 | 489 | extension AssetsViewController { 490 | func updateDisableCells() { 491 | collectionView?.visibleCells.compactMap { 492 | $0 as? AssetCell 493 | }.filter { cell in 494 | return !selectedIndexPaths.contains(where: { 495 | $0.item == cell.tag 496 | }) 497 | }.forEach { cell in 498 | let item = cell.tag 499 | let asset = assetsFetchResults[item] 500 | if asset.mediaType == .video { 501 | cell.isDisabled = !canSelectVideo 502 | } else { 503 | cell.isDisabled = !canSelectImage 504 | } 505 | } 506 | } 507 | 508 | func clearSelectedCell(at indexPath: IndexPath) { 509 | selectedIndexPaths.forEach({ (selectIndexPath) in 510 | if selectIndexPath != indexPath { 511 | deselectItemAtIndexPath(selectIndexPath, uncheckCell: true) 512 | } 513 | }) 514 | selectedIndexPaths.removeAll() 515 | } 516 | 517 | func selectItemAtIndexPath(_ indexPath: IndexPath) { 518 | let asset = assetsFetchResults[indexPath.item] 519 | 520 | if photoPickerController.allowMultipleSelection { 521 | if asset.mediaType == .video { 522 | if !photoPickerController.enableVideoMultipleSelection { 523 | clearSelectedCell(at: indexPath) 524 | } 525 | toggleHighQualityButtonHidden(true) 526 | } else if isVideoAsset(lastSelectItemIndexPath) { 527 | clearSelectedCell(at: indexPath) 528 | toggleHighQualityButtonHidden(false) 529 | } else { 530 | toggleHighQualityButtonHidden(false) 531 | } 532 | 533 | selectedAssets.append(asset) 534 | selectedIndexPaths.append(indexPath) 535 | 536 | if photoPickerController.enableVideoMultipleSelection { 537 | canSelectVideo = asset.mediaType == .image ? false : !isMaximumSelectionReached() 538 | } else { 539 | canSelectVideo = false 540 | } 541 | canSelectImage = asset.mediaType == .video ? false : !isMaximumSelectionReached() 542 | 543 | lastSelectItemIndexPath = indexPath 544 | 545 | updateDisableCells() 546 | updateToolBar() 547 | } else { 548 | photoPickerController.delegate?.photoPickerController(photoPickerController, didFinishPickingAssets: [asset], needHighQualityImage: true) 549 | } 550 | 551 | photoPickerController.delegate?.photoPickerController(photoPickerController, didSelectAsset: asset) 552 | } 553 | 554 | func deselectItemAtIndexPath(_ indexPath: IndexPath, uncheckCell: Bool) { 555 | guard photoPickerController.allowMultipleSelection else { return } 556 | let asset = assetsFetchResults[indexPath.item] 557 | if let index = selectedAssets.firstIndex(of: asset) { 558 | let asset = selectedAssets[index] 559 | selectedAssets.remove(at: index) 560 | selectedIndexPaths.remove(at: index) 561 | lastSelectItemIndexPath = nil 562 | if photoPickerController.enableVideoMultipleSelection && asset.mediaType == .video { 563 | canSelectVideo = true 564 | canSelectImage = selectedAssets.isEmpty 565 | } else { 566 | canSelectVideo = selectedAssets.isEmpty 567 | canSelectImage = true 568 | } 569 | updateDisableCells() 570 | updateToolBar() 571 | photoPickerController.delegate?.photoPickerController(photoPickerController, didDeselectAsset: asset) 572 | if let cell = collectionView?.cellForItem(at: indexPath) as? AssetCell, uncheckCell { 573 | cell.setChecked(false, animation: true) 574 | } 575 | } 576 | } 577 | 578 | func shouldSelectItemAtIndexPath(_ indexPath: IndexPath, checked: Bool) -> Bool { 579 | if checked { 580 | return true 581 | } 582 | 583 | let asset = assetsFetchResults[indexPath.item] 584 | 585 | if asset.mediaType == .video, !canSelectVideo { 586 | return false 587 | } 588 | 589 | if asset.mediaType == .image, !canSelectImage { 590 | return false 591 | } 592 | 593 | guard let shouldSelectAsset = photoPickerController.delegate?.photoPickerController(photoPickerController, shouldSelectAsset: asset), shouldSelectAsset else { 594 | return false 595 | } 596 | 597 | return true 598 | } 599 | } 600 | 601 | //MARK: - UICollectionViewDelegateFlowLayout 602 | extension AssetsViewController: UICollectionViewDelegateFlowLayout { 603 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 604 | var numberOfColumns: Int = 0 605 | switch (currentDevice, currentOrientation) { 606 | case (.phone, .landscape): 607 | numberOfColumns = AssetsNumberOfColumns.LandscapePhone 608 | case (.phone, .portrait): 609 | numberOfColumns = AssetsNumberOfColumns.PortraitPhone 610 | case (.pad, .landscape): 611 | numberOfColumns = AssetsNumberOfColumns.LandscapePad 612 | case (.pad, .portrait): 613 | numberOfColumns = AssetsNumberOfColumns.PortraitPad 614 | default: 615 | numberOfColumns = AssetsNumberOfColumns.LandscapePhone 616 | } 617 | 618 | let containerViewWidth = self.view.frame.width 619 | let width: CGFloat = floor((containerViewWidth - 2.0 * CGFloat(numberOfColumns - 1)) / CGFloat(numberOfColumns)) 620 | return CGSize(width: width, height: width) 621 | } 622 | } 623 | 624 | // MARK: - PHPhotoLibraryChangeObserver 625 | extension AssetsViewController: PHPhotoLibraryChangeObserver { 626 | func photoLibraryDidChange(_ changeInstance: PHChange) { 627 | DispatchQueue.main.async { [weak self]() -> Void in 628 | guard let strongSelf = self else { 629 | return 630 | } 631 | guard let collectionChanges = changeInstance.changeDetails(for: strongSelf.assetsFetchResults as! PHFetchResult) else { 632 | return 633 | } 634 | 635 | strongSelf.assetsFetchResults = collectionChanges.fetchResultAfterChanges as? PHFetchResult 636 | 637 | if !collectionChanges.hasIncrementalChanges || collectionChanges.hasMoves { 638 | strongSelf.collectionView?.reloadData() 639 | strongSelf.resetCachedAssets() 640 | } else { 641 | let removedIndexes = collectionChanges.removedIndexes 642 | let removedIndexPaths = removedIndexes?.pp_indexPathsFromIndexesInSection(0) 643 | 644 | let insertedIndexes = collectionChanges.insertedIndexes 645 | let insertedIndexPaths = insertedIndexes?.pp_indexPathsFromIndexesInSection(0) 646 | 647 | let changedIndexes = collectionChanges.changedIndexes 648 | let changedIndexPaths = changedIndexes?.pp_indexPathsFromIndexesInSection(0) 649 | 650 | var shouldReload = false 651 | if let removedIndexPaths = removedIndexPaths, let changedIndexPaths = changedIndexPaths { 652 | shouldReload = !Set(removedIndexPaths).isDisjoint(with: Set(changedIndexPaths)) 653 | } 654 | 655 | if let lastIndexPath = removedIndexPaths?.last, lastIndexPath.item >= strongSelf.assetsFetchResults.count { 656 | shouldReload = true 657 | } 658 | 659 | if shouldReload { 660 | strongSelf.collectionView.reloadData() 661 | } else { 662 | strongSelf.collectionView.performBatchUpdates({ [weak self] in 663 | guard let strongSelf = self else { 664 | return 665 | } 666 | if let removedIndexPaths = removedIndexPaths, !removedIndexPaths.isEmpty { 667 | strongSelf.collectionView?.deleteItems(at: removedIndexPaths) 668 | } 669 | if let insertedIndexPaths = insertedIndexPaths, !insertedIndexPaths.isEmpty { 670 | strongSelf.collectionView?.insertItems(at: insertedIndexPaths) 671 | } 672 | if let changedIndexPaths = changedIndexPaths, !changedIndexPaths.isEmpty { 673 | strongSelf.collectionView?.reloadItems(at: changedIndexPaths) 674 | } 675 | }, completion: { [weak self] (finished) in 676 | guard let strongSelf = self else { 677 | return 678 | } 679 | if finished { 680 | strongSelf.resetCachedAssets() 681 | } 682 | }) 683 | } 684 | } 685 | } 686 | } 687 | } 688 | 689 | // MARK: - PhotoBrowserDelegate 690 | extension AssetsViewController: PhotoBrowserDelegate { 691 | func photoBrowser(_ browser: PhotoBrowser, canSelectPhotoAtIndex index: Int) -> Bool { 692 | let indexPath = IndexPath(item: index, section: 0) 693 | let checked = selectedIndexPaths.contains(indexPath) 694 | return shouldSelectItemAtIndexPath(indexPath, checked: checked) 695 | } 696 | 697 | func photoBrowser(_ browser: PhotoBrowser, didSelectPhotoAtIndex index: Int) { 698 | let indexPath = IndexPath(item: index, section: 0) 699 | let checked = selectedIndexPaths.contains(indexPath) 700 | if !checked { 701 | selectItemAtIndexPath(indexPath) 702 | if let cell = collectionView?.cellForItem(at: indexPath) as? AssetCell { 703 | cell.setChecked(true, animation: true) 704 | } 705 | } else { 706 | deselectItemAtIndexPath(indexPath, uncheckCell: true) 707 | } 708 | } 709 | 710 | func photoBrowser(_ browser: PhotoBrowser, didShowPhotoAtIndex index: Int) { 711 | if let button = photoBrowserHighQualityButton, button.checked { 712 | button.highqualityImageSize = getImageSize(at: index) 713 | } 714 | 715 | guard index == browser.currentIndex else { 716 | return 717 | } 718 | 719 | let indexPath = IndexPath(item: index, section: 0) 720 | let checked = selectedIndexPaths.contains(indexPath) 721 | browser.enableSelect = self.shouldSelectItemAtIndexPath(indexPath, checked: checked) 722 | } 723 | } 724 | -------------------------------------------------------------------------------- /PhotoPicker/CircleProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircleProgressView.swift 3 | // PhotoPicker 4 | // 5 | // Created by Suric on 2018/11/25. 6 | // Copyright © 2018 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CircularProgressView: UIView { 12 | 13 | // progress: Should be between 0 to 1 14 | var progress: CGFloat = 0 { 15 | didSet { 16 | self.isHidden = self.progress == 1 17 | self.setNeedsDisplay() 18 | } 19 | } 20 | 21 | private var circleStrokeWidth: CGFloat = 5 22 | 23 | // MARK: Initializers 24 | 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | self.backgroundColor = UIColor.clear 28 | self.isHidden = true 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | super.init(coder: aDecoder) 33 | self.isHidden = true 34 | } 35 | 36 | // MARK: Public Methods 37 | 38 | func setCircleStrokeWidth(_ circleStrokeWidth: CGFloat) { 39 | self.circleStrokeWidth = circleStrokeWidth 40 | self.setNeedsDisplay() 41 | } 42 | 43 | // MARK: Core Graphics Drawing 44 | 45 | override func draw(_ rect: CGRect) { 46 | super.draw(rect) 47 | drawOutCircle(rect) 48 | drawOutLine(rect) 49 | drawInnerLine(rect) 50 | drawRect(rect, margin: circleStrokeWidth, color: UIColor.white, percentage: progress) 51 | drawProgressCircle(rect) 52 | } 53 | 54 | func drawCircle(with rect: CGRect, lineWidth: CGFloat, strokeColor: UIColor, margin: CGFloat = 0) { 55 | let context: CGContext = UIGraphicsGetCurrentContext()! 56 | context.setLineWidth(lineWidth) 57 | context.setStrokeColor(strokeColor.cgColor) 58 | 59 | let centerX: CGFloat = rect.width * 0.5 60 | let centerY: CGFloat = rect.height * 0.5 61 | let radius: CGFloat = min(rect.height, rect.width) * 0.5 - margin - lineWidth 62 | let startAngle: CGFloat = -.pi/2 63 | let endAngle: CGFloat = -.pi/2 + .pi * 2 64 | let center: CGPoint = CGPoint(x: centerX, y: centerY) 65 | 66 | context.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false) 67 | context.strokePath() 68 | } 69 | 70 | func drawOutCircle(_ rect: CGRect) { 71 | drawCircle(with: rect, lineWidth: circleStrokeWidth / 2, strokeColor: UIColor.white) 72 | } 73 | 74 | func drawOutLine(_ rect: CGRect) { 75 | drawCircle(with: rect, lineWidth: 0.5, strokeColor: UIColor.gray) 76 | } 77 | 78 | 79 | func drawInnerLine(_ rect: CGRect) { 80 | drawCircle(with: rect, lineWidth: 0.5, strokeColor: UIColor.gray, margin: circleStrokeWidth / 2) 81 | } 82 | 83 | private func drawRect(_ rect: CGRect, margin: CGFloat, color: UIColor, percentage: CGFloat) { 84 | 85 | let radius: CGFloat = min(rect.height, rect.width) * 0.5 - margin 86 | let centerX: CGFloat = rect.width * 0.5 87 | let centerY: CGFloat = rect.height * 0.5 88 | 89 | let context: CGContext = UIGraphicsGetCurrentContext()! 90 | context.setStrokeColor(UIColor.white.cgColor) 91 | context.setFillColor(UIColor.white.cgColor) 92 | let center: CGPoint = CGPoint(x: centerX, y: centerY) 93 | context.move(to: center) 94 | let startAngle: CGFloat = -.pi/2 95 | let endAngle: CGFloat = -.pi/2 + .pi * 2 * percentage 96 | context.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false) 97 | context.closePath() 98 | context.fillPath() 99 | } 100 | 101 | private func drawProgressCircle(_ rect: CGRect) { 102 | let context: CGContext = UIGraphicsGetCurrentContext()! 103 | context.setStrokeColor(UIColor.gray.cgColor) 104 | context.setFillColor(UIColor.white.cgColor) 105 | context.setLineWidth(0.5) 106 | 107 | let centerX: CGFloat = rect.width * 0.5 108 | let centerY: CGFloat = rect.height * 0.5 109 | let radius: CGFloat = min(rect.height, rect.width) * 0.5 - (circleStrokeWidth / 2) - 0.5 110 | let startAngle: CGFloat = -.pi/2 111 | let endAngle: CGFloat = -.pi/2 + .pi * 2 * progress 112 | let center: CGPoint = CGPoint(x: centerX, y: centerY) 113 | 114 | context.move(to: center) 115 | context.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false) 116 | context.closePath() 117 | context.drawPath(using: .fillStroke) 118 | 119 | drawProgressLine(rect) 120 | } 121 | 122 | func drawProgressLine(_ rect: CGRect) { 123 | let context: CGContext = UIGraphicsGetCurrentContext()! 124 | context.setLineWidth(0.5) 125 | context.setStrokeColor(UIColor.white.cgColor) 126 | 127 | let centerX: CGFloat = rect.width * 0.5 128 | let centerY: CGFloat = rect.height * 0.5 129 | let radius: CGFloat = min(rect.height, rect.width) * 0.5 - (circleStrokeWidth / 2) - 0.5 130 | let startAngle: CGFloat = -.pi/2 131 | let endAngle: CGFloat = -.pi/2 + .pi * 2 * progress 132 | let center: CGPoint = CGPoint(x: centerX, y: centerY) 133 | 134 | context.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false) 135 | context.strokePath() 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /PhotoPicker/CommonExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonExtension.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGSize { 12 | func scale(_ scale: CGFloat) -> CGSize { 13 | return CGSize(width: self.width * scale, height: self.height * scale) 14 | } 15 | } 16 | 17 | extension UICollectionView { 18 | func pp_indexPathsForElementsInRect(_ rect: CGRect) -> [IndexPath]? { 19 | let allLayoutAttributes = self.collectionViewLayout.layoutAttributesForElements(in: rect) 20 | 21 | guard let attributes = allLayoutAttributes , attributes.count > 0 else { 22 | return nil 23 | } 24 | 25 | return attributes.compactMap { $0.indexPath } 26 | } 27 | } 28 | 29 | extension IndexSet { 30 | func pp_indexPathsFromIndexesInSection(_ section: Int) -> [IndexPath] { 31 | var indexPaths: [IndexPath] = [] 32 | for (index, _) in self.enumerated() { 33 | indexPaths.append(IndexPath(item: index, section: section)) 34 | } 35 | return indexPaths 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PhotoPicker/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.4.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PhotoPicker/PhotoPicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoPicker.h 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for PhotoPicker. 12 | FOUNDATION_EXPORT double PhotoPickerVersionNumber; 13 | 14 | //! Project version string for PhotoPicker. 15 | FOUNDATION_EXPORT const unsigned char PhotoPickerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /PhotoPicker/PhotoPicker.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 179 | 206 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | -------------------------------------------------------------------------------- /PhotoPicker/PhotoPickerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoPickerController.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | open class PhotoPickerController: UIViewController { 13 | 14 | //MARK: - public property 15 | open weak var delegate: PhotoPickerDelegate? 16 | open var assetCollectionSubtypes: [PHAssetCollectionSubtype]? 17 | open var allowMultipleSelection: Bool = true 18 | open var enableVideoMultipleSelection: Bool = false 19 | open var minimumNumberOfSelection: Int = 1 20 | open var maximumNumberOfSelection: Int = 9 21 | open var mediaType: PhotoPickerMediaType = .any 22 | open var prompt: String? 23 | 24 | //MARK: - private property 25 | fileprivate var albumsNavigationController: UINavigationController! 26 | 27 | public init(localizedStrings: [String: String], highQualityImageByDefault: Bool = false) { 28 | super.init(nibName:nil, bundle:nil) 29 | 30 | self.assetCollectionSubtypes = [.smartAlbumUserLibrary, 31 | .smartAlbumFavorites, 32 | .albumMyPhotoStream, 33 | .albumCloudShared, 34 | .albumRegular] 35 | 36 | if let color = PhotoPickerThemeManager.shared.themeColor { 37 | themeToolBarTintColor = color 38 | themeTextColor = color 39 | } 40 | setupAlbumsViewController() 41 | localizedString = localizedStrings 42 | 43 | let albumsViewController = albumsNavigationController.topViewController as! AlbumsViewController 44 | albumsViewController.photoPickerController = self 45 | albumsViewController.highQualityImageByDefault = highQualityImageByDefault 46 | } 47 | 48 | func setupAlbumsViewController() { 49 | let storyboard = UIStoryboard(name: "PhotoPicker", bundle: currentBundle) 50 | let navigationController: UINavigationController = storyboard.instantiateViewController(withIdentifier: "AlbumsNavigationController") as! UINavigationController 51 | 52 | addChild(navigationController) 53 | navigationController.view.frame = view.bounds 54 | view.addSubview(navigationController.view) 55 | navigationController.didMove(toParent: self) 56 | 57 | albumsNavigationController = navigationController 58 | } 59 | 60 | public required init?(coder aDecoder: NSCoder) { 61 | fatalError("init(coder:) has not been implemented") 62 | } 63 | 64 | public static func updatePhotoPickerTheme(themeColor: UIColor) { 65 | PhotoPickerThemeManager.shared.themeColor = themeColor 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /PhotoPicker/PhotoPickerProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoPickerProtocol.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import Photos 10 | 11 | public protocol PhotoPickerDelegate: class { 12 | func photoPickerController(_ controller: PhotoPickerController, didFinishPickingAssets assets: [PHAsset], needHighQualityImage: Bool) 13 | func photoPickerControllerDidCancel(_ controller: PhotoPickerController) 14 | func photoPickerController(_ controller: PhotoPickerController, shouldSelectAsset asset: PHAsset) -> Bool 15 | func photoPickerController(_ controller: PhotoPickerController, didSelectAsset asset: PHAsset) 16 | func photoPickerController(_ controller: PhotoPickerController, didDeselectAsset asset: PHAsset) 17 | } 18 | 19 | public extension PhotoPickerDelegate { 20 | func photoPickerController(_ controller: PhotoPickerController, didFinishPickingAssets assets: [PHAsset], needHighQualityImage: Bool) {} 21 | 22 | func photoPickerControllerDidCancel(_ controller: PhotoPickerController) {} 23 | 24 | func photoPickerController(_ controller: PhotoPickerController, shouldSelectAsset asset: PHAsset) -> Bool { 25 | return true 26 | } 27 | 28 | func photoPickerController(_ controller: PhotoPickerController, didSelectAsset asset: PHAsset) {} 29 | 30 | func photoPickerController(_ controller: PhotoPickerController, didDeselectAsset asset: PHAsset) {} 31 | } 32 | -------------------------------------------------------------------------------- /PhotoPicker/PhotoPickerThemeManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoPickerThemeManager.swift 3 | // PhotoPicker 4 | // 5 | // Created by Yan on 2017/5/18. 6 | // Copyright © 2017年 StormXX. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class PhotoPickerThemeManager { 12 | 13 | static let shared = PhotoPickerThemeManager() 14 | 15 | var themeColor: UIColor? 16 | 17 | private init() { } 18 | } 19 | -------------------------------------------------------------------------------- /PhotoPicker/SlomoIconView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SlomoIconView.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/6/21. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SlomoIconView: UIView { 12 | 13 | var iconColor: UIColor = UIColor.white 14 | 15 | override func draw(_ rect: CGRect) { 16 | iconColor.setStroke() 17 | let width: CGFloat = 2.2 18 | let insetRect = rect.insetBy(dx: width / 2, dy: width / 2) 19 | 20 | let circlePath = UIBezierPath(ovalIn: insetRect) 21 | circlePath.lineWidth = width 22 | let pattern: [CGFloat] = [0.75, 0.75] 23 | circlePath.setLineDash(pattern, count: 2, phase: 0) 24 | circlePath.stroke() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PhotoPicker/ToolBarHighQualityButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolBarHighQualityButton.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/3/1. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let imageViewWidth: CGFloat = 18 12 | typealias HighQualityButtonAction = (Bool) -> Void 13 | 14 | class ToolBarHighQualityButton: UIView { 15 | weak var assetsViewController: AssetsViewController! 16 | 17 | //MARK: - public property 18 | var checked: Bool = false { 19 | didSet { 20 | if checked { 21 | imageView.image = UIImage(named: toolbarHighQualityImageCheckedImageName, in: currentBundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) 22 | titleLabel.textColor = themeTextColor 23 | assetsViewController.updateHighQualityImageSize() 24 | } else { 25 | imageView.image = UIImage(named: toolbarHighQualityImageUnCheckedImageName, in: currentBundle, compatibleWith: nil) 26 | titleLabel.textColor = greyTextColor 27 | highqualityImageSize = 0 28 | } 29 | } 30 | } 31 | 32 | var highqualityImageSize: Int = 0 { 33 | didSet { 34 | if highqualityImageSize == 0 { 35 | titleLabel.text = localizedString["PhotoPicker.Origin"] 36 | } else { 37 | let kb: Float = Float(highqualityImageSize) / 1024.0 38 | if kb > 1024.0 { 39 | let mb: Float = kb / 1024.0 40 | titleLabel.text = "\(localizedString["PhotoPicker.Origin"]!)(\(String(format: "%.2f", mb))M)" 41 | } else { 42 | titleLabel.text = "\(localizedString["PhotoPicker.Origin"]!)(\(String(format: "%.2f", kb))K)" 43 | } 44 | } 45 | } 46 | } 47 | 48 | //MARK: - private property 49 | fileprivate var imageView: UIImageView! 50 | fileprivate var titleLabel: UILabel! 51 | fileprivate var tap: UITapGestureRecognizer! 52 | var action: HighQualityButtonAction? 53 | 54 | override init(frame: CGRect) { 55 | super.init(frame: frame) 56 | setupProperty() 57 | addSubview(imageView) 58 | addSubview(titleLabel) 59 | setupConstraints() 60 | } 61 | 62 | func setupProperty() { 63 | imageView = UIImageView(frame: CGRect.zero) 64 | imageView.image = UIImage(named: toolbarHighQualityImageUnCheckedImageName, in: currentBundle, compatibleWith: nil) 65 | 66 | titleLabel = UILabel(frame: CGRect.zero) 67 | titleLabel.textColor = greyTextColor 68 | titleLabel.font = UIFont.systemFont(ofSize: 15.0) 69 | titleLabel.text = localizedString["PhotoPicker.Origin"] 70 | 71 | tap = UITapGestureRecognizer(target: self, action: #selector(ToolBarHighQualityButton.tapped(_:))) 72 | addGestureRecognizer(tap) 73 | } 74 | 75 | func setupConstraints() { 76 | if #available(iOS 11.0, *) { 77 | self.heightAnchor.constraint(equalToConstant: 21.0).isActive = true 78 | } 79 | 80 | imageView.translatesAutoresizingMaskIntoConstraints = false 81 | titleLabel.translatesAutoresizingMaskIntoConstraints = false 82 | 83 | let views = ["imageView": imageView!, "titleLabel": titleLabel!] as [String : Any] 84 | let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[imageView(==imageViewWidth)]-5-[titleLabel]|", options: [], metrics: ["imageViewWidth": imageViewWidth], views: views) 85 | let imageViewHeight = NSLayoutConstraint(item: imageView!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: imageViewWidth) 86 | let imageViewCenterY = NSLayoutConstraint(item: imageView!, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0.0) 87 | let titleLabelCenterY = NSLayoutConstraint(item: titleLabel!, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0.0) 88 | let constraints = [imageViewHeight, imageViewCenterY, titleLabelCenterY] 89 | 90 | NSLayoutConstraint.activate(horizontalConstraints + constraints) 91 | } 92 | 93 | @objc func tapped(_ recognizer: UIGestureRecognizer) { 94 | checked = !checked 95 | action?(checked) 96 | } 97 | 98 | required init?(coder aDecoder: NSCoder) { 99 | fatalError("init(coder:) has not been implemented") 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /PhotoPicker/ToolBarNumberView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolBarNumberView.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/29. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ToolBarNumberView: UIView { 12 | var number: Int = 0 { 13 | didSet { 14 | if number == 0 { 15 | imageView.isHidden = true 16 | numberLabel.isHidden = true 17 | } else { 18 | imageView.isHidden = false 19 | numberLabel.isHidden = false 20 | numberLabel.text = "\(number)" 21 | changeNumberAnimation() 22 | } 23 | } 24 | } 25 | 26 | fileprivate var imageView: UIImageView! 27 | fileprivate var numberLabel: UILabel! 28 | 29 | override init(frame: CGRect) { 30 | super.init(frame: frame) 31 | 32 | setupProperty() 33 | addSubview(imageView) 34 | addSubview(numberLabel) 35 | } 36 | 37 | func setupProperty() { 38 | imageView = UIImageView(frame: bounds) 39 | imageView.image = UIImage(named: toolbarNumberViewBackgroundImageName, in: currentBundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) 40 | imageView.isHidden = true 41 | 42 | numberLabel = UILabel(frame: bounds) 43 | numberLabel.font = UIFont.boldSystemFont(ofSize: 15.0) 44 | numberLabel.textAlignment = .center 45 | numberLabel.textColor = UIColor.white 46 | numberLabel.isHidden = true 47 | } 48 | 49 | func changeNumberAnimation() { 50 | UIView.animate(withDuration: 0.1, animations: { [weak self] in 51 | self?.imageView.transform = CGAffineTransform(scaleX: 0, y: 0) 52 | }, completion: { (finished) -> Void in 53 | UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 10.0, options: [], animations: { 54 | self.imageView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) 55 | }, completion: nil) 56 | }) 57 | } 58 | 59 | required init?(coder aDecoder: NSCoder) { 60 | fatalError("init(coder:) has not been implemented") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /PhotoPicker/UIView+Frame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewExtension.swift 3 | // STNumberLabel 4 | // 5 | // Created by DangGu on 15/11/16. 6 | // Copyright © 2015年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | var width: CGFloat { 13 | return bounds.width 14 | } 15 | 16 | var height: CGFloat { 17 | return bounds.height 18 | } 19 | 20 | var minX: CGFloat { 21 | return bounds.minX 22 | } 23 | 24 | var minY: CGFloat { 25 | return bounds.minY 26 | } 27 | 28 | var maxX: CGFloat { 29 | return bounds.maxX 30 | } 31 | 32 | var maxY: CGFloat { 33 | return bounds.maxY 34 | } 35 | 36 | var midY: CGFloat { 37 | return bounds.midY 38 | } 39 | 40 | var absoluteCenter: CGPoint { 41 | return CGPoint(x: width / 2, y: height / 2) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PhotoPicker/VideoIconView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoIconView.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/6/21. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class VideoIconView: UIView { 12 | 13 | var iconColor: UIColor = UIColor.white 14 | 15 | override func draw(_ rect: CGRect) { 16 | iconColor.setFill() 17 | 18 | let trianglePath = UIBezierPath() 19 | trianglePath.move(to: CGPoint(x: maxX, y: minY)) 20 | trianglePath.addLine(to: CGPoint(x: maxX, y: maxY)) 21 | trianglePath.addLine(to: CGPoint(x: maxX - midY, y: midY)) 22 | trianglePath.close() 23 | trianglePath.fill() 24 | 25 | let squarePath = UIBezierPath(roundedRect: CGRect(x: minX, y: minY, width: width - midY - 1.0, height: height), cornerRadius: 2.0) 26 | squarePath.fill() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PhotoPicker/VideoIndicatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoIndicatorView.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/6/21. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class VideoIndicatorView: UIView { 12 | 13 | @IBOutlet weak var videoIcon: VideoIconView! 14 | @IBOutlet weak var slomoIcon: SlomoIconView! 15 | @IBOutlet weak var timeLabel: UILabel! 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | 20 | let gradientLayer = CAGradientLayer() 21 | gradientLayer.frame = bounds 22 | gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor] 23 | layer.insertSublayer(gradientLayer, at: 0) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /PhotoPicker/constant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // constant.swift 3 | // PhotoPicker 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | var themeToolBarTintColor: UIColor = UIColor(red: 3/255.0, green: 169/255.0, blue: 244/255.0, alpha: 1.0) 12 | let greyTextColor: UIColor = UIColor(red: 191/255.0, green: 191/255.0, blue: 191/255.0, alpha: 1.0) 13 | var themeTextColor: UIColor = UIColor(red: 3/255.0, green: 169/255.0, blue: 244/255.0, alpha: 1.0) 14 | let AlbumCoverBorderColor: CGColor = UIColor.white.cgColor 15 | let currentBundle: Bundle = Bundle(for: PhotoPickerController.self) 16 | let selectedCheckMarkImageName = "checkmark_selected" 17 | let unselectedCheckMarkImageName = "checkmark_unselected" 18 | let toolbarNumberViewBackgroundImageName = "toolbar_numberview_background" 19 | let toolbarHighQualityImageCheckedImageName = "toolbar_highquality_checked" 20 | let toolbarHighQualityImageUnCheckedImageName = "toolbar_highquality_unchecked" 21 | 22 | let assetCellIdentifier = "AssetCell" 23 | let albumCellIdentifier = "AlbumCell" 24 | let assetFooterViewIdentifier = "AssetFooterView" 25 | 26 | public enum PhotoPickerMediaType { 27 | case any 28 | case image 29 | case video 30 | } 31 | 32 | enum PhotoPickerOrientation { 33 | case landscape 34 | case portrait 35 | } 36 | 37 | struct AssetsNumberOfColumns { 38 | static let PortraitPhone: Int = 4 39 | static let PortraitPad: Int = 5 40 | static let LandscapePhone: Int = 6 41 | static let LandscapePad: Int = 6 42 | } 43 | 44 | var currentOrientation: PhotoPickerOrientation { 45 | get { 46 | var orientation: PhotoPickerOrientation = .portrait 47 | switch UIApplication.shared.statusBarOrientation { 48 | case .portrait, .portraitUpsideDown, .unknown: 49 | orientation = .portrait 50 | case .landscapeLeft, .landscapeRight: 51 | orientation = .landscape 52 | @unknown default: 53 | orientation = .portrait 54 | } 55 | return orientation 56 | } 57 | } 58 | 59 | var currentDevice: UIUserInterfaceIdiom { 60 | get { 61 | return UIDevice.current.userInterfaceIdiom 62 | } 63 | } 64 | 65 | var localizedString: [String: String] = [ 66 | "PhotoPicker.Cancel": "取消", 67 | "PhotoPicker.OK": "确定", 68 | "PhotoPicker.Send": "发送", 69 | "PhotoPicker.Origin": "原图", 70 | "PhotoPicker.Photos": "张照片", 71 | "PhotoPicker.Videos": "个视频", 72 | "PhotoPicker.Title" : "照片" 73 | ] 74 | -------------------------------------------------------------------------------- /PhotoPickerDemo.xcodeproj/PhotoPicker.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PhotoPickerDemo.xcodeproj/PhotoPicker.xcworkspace/xcuserdata/StormXX.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/PhotoPickerDemo.xcodeproj/PhotoPicker.xcworkspace/xcuserdata/StormXX.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PhotoPickerDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EB31805B1D8CE55D00443236 /* PhotoBrowser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB31805A1D8CE55D00443236 /* PhotoBrowser.framework */; }; 11 | EB31805C1D8CE55D00443236 /* PhotoBrowser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EB31805A1D8CE55D00443236 /* PhotoBrowser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | EB31805E1D8CE59C00443236 /* Kingfisher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB31805D1D8CE59C00443236 /* Kingfisher.framework */; }; 13 | EB31805F1D8CE59C00443236 /* Kingfisher.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EB31805D1D8CE59C00443236 /* Kingfisher.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | EB7B2C081D9A6558007878B8 /* PhotoPicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB7B2C071D9A6558007878B8 /* PhotoPicker.framework */; }; 15 | EB7B2C091D9A6558007878B8 /* PhotoPicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EB7B2C071D9A6558007878B8 /* PhotoPicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 16 | EBA70AEE1C7EA94B0089ECD7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA70AED1C7EA94B0089ECD7 /* AppDelegate.swift */; }; 17 | EBA70AF01C7EA94B0089ECD7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA70AEF1C7EA94B0089ECD7 /* ViewController.swift */; }; 18 | EBA70AF31C7EA94B0089ECD7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EBA70AF11C7EA94B0089ECD7 /* Main.storyboard */; }; 19 | EBA70AF51C7EA94B0089ECD7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBA70AF41C7EA94B0089ECD7 /* Assets.xcassets */; }; 20 | EBA70AF81C7EA94B0089ECD7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EBA70AF61C7EA94B0089ECD7 /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | EB3180591D8CE52A00443236 /* Embed Frameworks */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = ""; 28 | dstSubfolderSpec = 10; 29 | files = ( 30 | EB31805C1D8CE55D00443236 /* PhotoBrowser.framework in Embed Frameworks */, 31 | EB7B2C091D9A6558007878B8 /* PhotoPicker.framework in Embed Frameworks */, 32 | EB31805F1D8CE59C00443236 /* Kingfisher.framework in Embed Frameworks */, 33 | ); 34 | name = "Embed Frameworks"; 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXCopyFilesBuildPhase section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | EB31805A1D8CE55D00443236 /* PhotoBrowser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PhotoBrowser.framework; path = Carthage/Build/iOS/PhotoBrowser.framework; sourceTree = ""; }; 41 | EB31805D1D8CE59C00443236 /* Kingfisher.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Kingfisher.framework; path = Carthage/Build/iOS/Kingfisher.framework; sourceTree = ""; }; 42 | EB7B2C071D9A6558007878B8 /* PhotoPicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PhotoPicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | EBA70AEA1C7EA94B0089ECD7 /* PhotoPickerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PhotoPickerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | EBA70AED1C7EA94B0089ECD7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | EBA70AEF1C7EA94B0089ECD7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 46 | EBA70AF21C7EA94B0089ECD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | EBA70AF41C7EA94B0089ECD7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | EBA70AF71C7EA94B0089ECD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | EBA70AF91C7EA94B0089ECD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | EBA70AE71C7EA94B0089ECD7 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | EB31805B1D8CE55D00443236 /* PhotoBrowser.framework in Frameworks */, 58 | EB7B2C081D9A6558007878B8 /* PhotoPicker.framework in Frameworks */, 59 | EB31805E1D8CE59C00443236 /* Kingfisher.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | EBA70AE11C7EA94B0089ECD7 = { 67 | isa = PBXGroup; 68 | children = ( 69 | EB7B2C071D9A6558007878B8 /* PhotoPicker.framework */, 70 | EB31805D1D8CE59C00443236 /* Kingfisher.framework */, 71 | EB31805A1D8CE55D00443236 /* PhotoBrowser.framework */, 72 | EBA70AEC1C7EA94B0089ECD7 /* PhotoPickerDemo */, 73 | EBA70AEB1C7EA94B0089ECD7 /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | EBA70AEB1C7EA94B0089ECD7 /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | EBA70AEA1C7EA94B0089ECD7 /* PhotoPickerDemo.app */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | EBA70AEC1C7EA94B0089ECD7 /* PhotoPickerDemo */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | EBA70AED1C7EA94B0089ECD7 /* AppDelegate.swift */, 89 | EBA70AEF1C7EA94B0089ECD7 /* ViewController.swift */, 90 | EBA70AF11C7EA94B0089ECD7 /* Main.storyboard */, 91 | EBA70AF41C7EA94B0089ECD7 /* Assets.xcassets */, 92 | EBA70AF61C7EA94B0089ECD7 /* LaunchScreen.storyboard */, 93 | EBA70AF91C7EA94B0089ECD7 /* Info.plist */, 94 | ); 95 | path = PhotoPickerDemo; 96 | sourceTree = ""; 97 | }; 98 | /* End PBXGroup section */ 99 | 100 | /* Begin PBXNativeTarget section */ 101 | EBA70AE91C7EA94B0089ECD7 /* PhotoPickerDemo */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = EBA70AFC1C7EA94C0089ECD7 /* Build configuration list for PBXNativeTarget "PhotoPickerDemo" */; 104 | buildPhases = ( 105 | EBA70AE61C7EA94B0089ECD7 /* Sources */, 106 | EBA70AE71C7EA94B0089ECD7 /* Frameworks */, 107 | EBA70AE81C7EA94B0089ECD7 /* Resources */, 108 | EB3180591D8CE52A00443236 /* Embed Frameworks */, 109 | ); 110 | buildRules = ( 111 | ); 112 | dependencies = ( 113 | ); 114 | name = PhotoPickerDemo; 115 | productName = PhotoPickerDemo; 116 | productReference = EBA70AEA1C7EA94B0089ECD7 /* PhotoPickerDemo.app */; 117 | productType = "com.apple.product-type.application"; 118 | }; 119 | /* End PBXNativeTarget section */ 120 | 121 | /* Begin PBXProject section */ 122 | EBA70AE21C7EA94B0089ECD7 /* Project object */ = { 123 | isa = PBXProject; 124 | attributes = { 125 | LastSwiftUpdateCheck = 0720; 126 | LastUpgradeCheck = 1020; 127 | ORGANIZATIONNAME = StormXX; 128 | TargetAttributes = { 129 | EBA70AE91C7EA94B0089ECD7 = { 130 | CreatedOnToolsVersion = 7.2.1; 131 | LastSwiftMigration = 0900; 132 | ProvisioningStyle = Manual; 133 | }; 134 | }; 135 | }; 136 | buildConfigurationList = EBA70AE51C7EA94B0089ECD7 /* Build configuration list for PBXProject "PhotoPickerDemo" */; 137 | compatibilityVersion = "Xcode 3.2"; 138 | developmentRegion = en; 139 | hasScannedForEncodings = 0; 140 | knownRegions = ( 141 | en, 142 | Base, 143 | ); 144 | mainGroup = EBA70AE11C7EA94B0089ECD7; 145 | productRefGroup = EBA70AEB1C7EA94B0089ECD7 /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | EBA70AE91C7EA94B0089ECD7 /* PhotoPickerDemo */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXResourcesBuildPhase section */ 155 | EBA70AE81C7EA94B0089ECD7 /* Resources */ = { 156 | isa = PBXResourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | EBA70AF81C7EA94B0089ECD7 /* LaunchScreen.storyboard in Resources */, 160 | EBA70AF51C7EA94B0089ECD7 /* Assets.xcassets in Resources */, 161 | EBA70AF31C7EA94B0089ECD7 /* Main.storyboard in Resources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXSourcesBuildPhase section */ 168 | EBA70AE61C7EA94B0089ECD7 /* Sources */ = { 169 | isa = PBXSourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | EBA70AF01C7EA94B0089ECD7 /* ViewController.swift in Sources */, 173 | EBA70AEE1C7EA94B0089ECD7 /* AppDelegate.swift in Sources */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXSourcesBuildPhase section */ 178 | 179 | /* Begin PBXVariantGroup section */ 180 | EBA70AF11C7EA94B0089ECD7 /* Main.storyboard */ = { 181 | isa = PBXVariantGroup; 182 | children = ( 183 | EBA70AF21C7EA94B0089ECD7 /* Base */, 184 | ); 185 | name = Main.storyboard; 186 | sourceTree = ""; 187 | }; 188 | EBA70AF61C7EA94B0089ECD7 /* LaunchScreen.storyboard */ = { 189 | isa = PBXVariantGroup; 190 | children = ( 191 | EBA70AF71C7EA94B0089ECD7 /* Base */, 192 | ); 193 | name = LaunchScreen.storyboard; 194 | sourceTree = ""; 195 | }; 196 | /* End PBXVariantGroup section */ 197 | 198 | /* Begin XCBuildConfiguration section */ 199 | EBA70AFA1C7EA94C0089ECD7 /* Debug */ = { 200 | isa = XCBuildConfiguration; 201 | buildSettings = { 202 | ALWAYS_SEARCH_USER_PATHS = NO; 203 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 204 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 205 | CLANG_CXX_LIBRARY = "libc++"; 206 | CLANG_ENABLE_MODULES = YES; 207 | CLANG_ENABLE_OBJC_ARC = YES; 208 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 209 | CLANG_WARN_BOOL_CONVERSION = YES; 210 | CLANG_WARN_COMMA = YES; 211 | CLANG_WARN_CONSTANT_CONVERSION = YES; 212 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 213 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 214 | CLANG_WARN_EMPTY_BODY = YES; 215 | CLANG_WARN_ENUM_CONVERSION = YES; 216 | CLANG_WARN_INFINITE_RECURSION = YES; 217 | CLANG_WARN_INT_CONVERSION = YES; 218 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 220 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 221 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 222 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 223 | CLANG_WARN_STRICT_PROTOTYPES = YES; 224 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 228 | COPY_PHASE_STRIP = NO; 229 | DEBUG_INFORMATION_FORMAT = dwarf; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | ENABLE_TESTABILITY = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu99; 233 | GCC_DYNAMIC_NO_PIC = NO; 234 | GCC_NO_COMMON_BLOCKS = YES; 235 | GCC_OPTIMIZATION_LEVEL = 0; 236 | GCC_PREPROCESSOR_DEFINITIONS = ( 237 | "DEBUG=1", 238 | "$(inherited)", 239 | ); 240 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 241 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 242 | GCC_WARN_UNDECLARED_SELECTOR = YES; 243 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 244 | GCC_WARN_UNUSED_FUNCTION = YES; 245 | GCC_WARN_UNUSED_VARIABLE = YES; 246 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 247 | MTL_ENABLE_DEBUG_INFO = YES; 248 | ONLY_ACTIVE_ARCH = YES; 249 | SDKROOT = iphoneos; 250 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 251 | }; 252 | name = Debug; 253 | }; 254 | EBA70AFB1C7EA94C0089ECD7 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 264 | CLANG_WARN_BOOL_CONVERSION = YES; 265 | CLANG_WARN_COMMA = YES; 266 | CLANG_WARN_CONSTANT_CONVERSION = YES; 267 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 275 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNREACHABLE_CODE = YES; 281 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 282 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 283 | COPY_PHASE_STRIP = NO; 284 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 285 | ENABLE_NS_ASSERTIONS = NO; 286 | ENABLE_STRICT_OBJC_MSGSEND = YES; 287 | GCC_C_LANGUAGE_STANDARD = gnu99; 288 | GCC_NO_COMMON_BLOCKS = YES; 289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 291 | GCC_WARN_UNDECLARED_SELECTOR = YES; 292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 293 | GCC_WARN_UNUSED_FUNCTION = YES; 294 | GCC_WARN_UNUSED_VARIABLE = YES; 295 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 296 | MTL_ENABLE_DEBUG_INFO = NO; 297 | SDKROOT = iphoneos; 298 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 299 | VALIDATE_PRODUCT = YES; 300 | }; 301 | name = Release; 302 | }; 303 | EBA70AFD1C7EA94C0089ECD7 /* Debug */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | CODE_SIGN_IDENTITY = "iPhone Developer"; 308 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 309 | CODE_SIGN_STYLE = Manual; 310 | DEVELOPMENT_TEAM = ""; 311 | FRAMEWORK_SEARCH_PATHS = ( 312 | "$(inherited)", 313 | "$(PROJECT_DIR)/Carthage/Build/iOS", 314 | ); 315 | INFOPLIST_FILE = PhotoPickerDemo/Info.plist; 316 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 317 | PRODUCT_BUNDLE_IDENTIFIER = com.StormXX.PhotoPickerDemo; 318 | PRODUCT_NAME = "$(TARGET_NAME)"; 319 | PROVISIONING_PROFILE = ""; 320 | PROVISIONING_PROFILE_SPECIFIER = ""; 321 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 322 | SWIFT_VERSION = 5.0; 323 | TARGETED_DEVICE_FAMILY = "1,2"; 324 | }; 325 | name = Debug; 326 | }; 327 | EBA70AFE1C7EA94C0089ECD7 /* Release */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 331 | CODE_SIGN_IDENTITY = "iPhone Developer"; 332 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 333 | CODE_SIGN_STYLE = Manual; 334 | DEVELOPMENT_TEAM = ""; 335 | FRAMEWORK_SEARCH_PATHS = ( 336 | "$(inherited)", 337 | "$(PROJECT_DIR)/Carthage/Build/iOS", 338 | ); 339 | INFOPLIST_FILE = PhotoPickerDemo/Info.plist; 340 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 341 | PRODUCT_BUNDLE_IDENTIFIER = com.StormXX.PhotoPickerDemo; 342 | PRODUCT_NAME = "$(TARGET_NAME)"; 343 | PROVISIONING_PROFILE = ""; 344 | PROVISIONING_PROFILE_SPECIFIER = ""; 345 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 346 | SWIFT_VERSION = 5.0; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | }; 349 | name = Release; 350 | }; 351 | /* End XCBuildConfiguration section */ 352 | 353 | /* Begin XCConfigurationList section */ 354 | EBA70AE51C7EA94B0089ECD7 /* Build configuration list for PBXProject "PhotoPickerDemo" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | EBA70AFA1C7EA94C0089ECD7 /* Debug */, 358 | EBA70AFB1C7EA94C0089ECD7 /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | EBA70AFC1C7EA94C0089ECD7 /* Build configuration list for PBXNativeTarget "PhotoPickerDemo" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | EBA70AFD1C7EA94C0089ECD7 /* Debug */, 367 | EBA70AFE1C7EA94C0089ECD7 /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | /* End XCConfigurationList section */ 373 | }; 374 | rootObject = EBA70AE21C7EA94B0089ECD7 /* Project object */; 375 | } 376 | -------------------------------------------------------------------------------- /PhotoPickerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PhotoPickerDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PhotoPickerDemo.xcodeproj/project.xcworkspace/xcuserdata/StormXX.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-archive/PhotoPicker/1bebe7b1ca8be39ad612b328f75b3e1816db4007/PhotoPickerDemo.xcodeproj/project.xcworkspace/xcuserdata/StormXX.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PhotoPickerDemo.xcodeproj/xcuserdata/StormXX.xcuserdatad/xcschemes/PhotoPickerDemo.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 | -------------------------------------------------------------------------------- /PhotoPickerDemo.xcodeproj/xcuserdata/StormXX.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PhotoPickerDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | EBA70AE91C7EA94B0089ECD7 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /PhotoPickerDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PhotoPickerDemo 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // 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. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /PhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /PhotoPickerDemo/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 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /PhotoPickerDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /PhotoPickerDemo/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.4.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSPhotoLibraryUsageDescription 26 | 此 App 需要您的同意才能够读取您的相册 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | UIInterfaceOrientationPortraitUpsideDown 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /PhotoPickerDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PhotoPickerDemo 4 | // 5 | // Created by DangGu on 16/2/25. 6 | // Copyright © 2016年 StormXX. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PhotoPicker 11 | import Photos 12 | 13 | class ViewController: UIViewController { 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | } 18 | 19 | @IBAction func showButtonTapped(_ sender: UIButton) { 20 | let localizedString: [String: String] = [ 21 | "PhotoPicker.Cancel": "取消", 22 | "PhotoPicker.OK": "确定", 23 | "PhotoPicker.Send": "发送", 24 | "PhotoPicker.Origin": "原图", 25 | "PhotoPicker.Photos": "张照片", 26 | "PhotoPicker.Videos": "个视频", 27 | "PhotoPicker.Title" : "照片" 28 | ] 29 | let photoPickerController = PhotoPickerController(localizedStrings: localizedString) 30 | photoPickerController.delegate = self 31 | photoPickerController.allowMultipleSelection = true 32 | photoPickerController.enableVideoMultipleSelection = true 33 | photoPickerController.maximumNumberOfSelection = 9 34 | photoPickerController.navigationController?.modalPresentationStyle = .formSheet 35 | let nav = UINavigationController(rootViewController: photoPickerController) 36 | // nav.modalPresentationStyle = .formSheet 37 | navigationController?.present(nav, animated: true, completion: nil) 38 | } 39 | } 40 | 41 | extension ViewController: PhotoPickerDelegate { 42 | func photoPickerControllerDidCancel(_ controller: PhotoPickerController) { 43 | navigationController?.dismiss(animated: true, completion: nil) 44 | } 45 | 46 | func photoPickerController(_ controller: PhotoPickerController, didFinishPickingAssets assets: [PHAsset], needHighQualityImage: Bool) { 47 | navigationController?.dismiss(animated: true, completion: { () -> Void in 48 | let alertController = UIAlertController(title: nil, message: "你已经选择了\(assets.count)张照片", preferredStyle: .alert) 49 | let cancelAction = UIAlertAction(title: "OK", style: .default) { (action) -> Void in 50 | alertController.dismiss(animated: true, completion: nil) 51 | } 52 | alertController.addAction(cancelAction) 53 | 54 | self.navigationController?.present(alertController, animated: true, completion: nil) 55 | }) 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhotoPicker 2 | A image picker for iOS , written by Swift. 3 | 4 | 5 | ## How To Get Started 6 | ### Carthage 7 | Specify "PhotoPicker" in your Cartfile: 8 | ``` 9 | github "teambition/PhotoPicker" 10 | ``` 11 | 12 | ### Usage 13 | ##### configuration properties 14 | ``` 15 | // use this to fetch what type album you want 16 | public var assetCollectionSubtypes: [PHAssetCollectionSubtype]? 17 | // if you want to select more than 1 photo, set it to true 18 | public var allowMultipleSelection: Bool 19 | 20 | ``` 21 | 22 | ##### Implement delegate 23 | ``` 24 | func photoPickerController(controller: PhotoPickerController, didFinishPickingAssets assets: [PHAsset], needHighQualityImage: Bool) 25 | func photoPickerControllerDidCancel(controller: PhotoPickerController) 26 | func photoPickerController(controller: PhotoPickerController, shouldSelectAsset asset: PHAsset) -> Bool 27 | func photoPickerController(controller: PhotoPickerController, didSelectAsset asset: PHAsset) 28 | func photoPickerController(controller: PhotoPickerController, didDeselectAsset asset: PHAsset) 29 | ``` 30 | 31 | ##### Present PhotoPicker 32 | ``` 33 | let photoPickerController = PhotoPickerController() 34 | photoPickerController.delegate = self 35 | presentViewController(photoPickerController, animated: true, completion: nil) 36 | ``` 37 | 38 | ## Similar 39 | - [QBImagePicker](https://github.com/questbeat/QBImagePicker)(This framework is written by Objective-C) 40 | - [Example app using Photos Framework](https://developer.apple.com/library/ios/samplecode/UsingPhotosFramework/Introduction/Intro.html)(Apple PhotoKit Sample Code) 41 | 42 | ## Minimum Requirement 43 | - iOS 8.0 44 | 45 | ## Release Notes 46 | - [Release Notes](https://github.com/teambition/PhotoPicker/releases) 47 | 48 | ## License 49 | - GrowingTextView is released under the MIT license. See [LICENSE](https://github.com/teambition/PhotoPicker/blob/master/LICENSE) for details. 50 | 51 | ## More Info 52 | - Have a question? Please [open an issue](https://github.com/teambition/PhotoPicker/issues/new)! 53 | -------------------------------------------------------------------------------- /TBPhotoPicker.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Created by teambition-ios on 2020/7/27. 3 | # Copyright © 2020 teambition. All rights reserved. 4 | # 5 | 6 | Pod::Spec.new do |s| 7 | s.name = 'TBPhotoPicker' 8 | s.version = '1.8.4' 9 | s.summary = 'A image picker for iOS , written by Swift.' 10 | s.description = <<-DESC 11 | A image picker for iOS , written by Swift. 12 | DESC 13 | 14 | s.homepage = 'https://github.com/teambition/PhotoPicker' 15 | s.license = { :type => 'MIT', :file => 'LICENSE' } 16 | s.author = { 'teambition mobile' => 'teambition-mobile@alibaba-inc.com' } 17 | s.source = { :git => 'https://github.com/teambition/PhotoPicker.git', :tag => s.version.to_s } 18 | 19 | s.swift_version = '5.0' 20 | s.ios.deployment_target = '10.0' 21 | 22 | s.source_files = 'PhotoPicker/*.swift', 'PhotoPicker/*.storyboard' 23 | 24 | s.dependency 'TBPhotoBrowser'#, :git => 'https://github.com/teambition/PhotoBrowser.git' 25 | # dependency无法指定git 26 | # PhotoBrowser仓库没有提交到Cocoapods 27 | # 在项目中pod PhotoPicker后还需要指定: 28 | # `pod 'PhotoBrowser', :git => 'https://github.com/teambition/PhotoBrowser.git'` 29 | 30 | end 31 | --------------------------------------------------------------------------------