├── .gitignore ├── .travis.yml ├── CLPhotoLib.podspec ├── CLPhotoLib ├── Assets │ ├── .gitkeep │ └── CLPhotoLib.bundle │ │ ├── Root.plist │ │ ├── btn_album_more@2x.png │ │ ├── btn_backItem_icon@2x.png │ │ ├── btn_backItem_icon@3x.png │ │ ├── btn_original_selected@2x.png │ │ ├── btn_original_selected@3x.png │ │ ├── btn_original_unselected@2x.png │ │ ├── btn_original_unselected@3x.png │ │ ├── btn_photo_selected@2x.png │ │ ├── btn_photo_selected@3x.png │ │ ├── btn_photo_unselected@2x.png │ │ ├── btn_photo_unselected@3x.png │ │ ├── btn_preview_play@2x.png │ │ ├── btn_preview_play@3x.png │ │ ├── clicon_default_photo.png │ │ ├── clicon_edit_left@2x.png │ │ ├── clicon_edit_left@3x.png │ │ ├── clicon_edit_right@2x.png │ │ ├── clicon_edit_right@3x.png │ │ ├── clicon_icloud_load@2x.png │ │ ├── clicon_icloud_load@3x.png │ │ ├── clicon_photo_arrow@2x.png │ │ ├── clicon_photo_live@2x.png │ │ ├── clicon_photo_live@3x.png │ │ ├── clicon_photo_shadow@2x.png │ │ ├── clicon_photo_shadow@3x.png │ │ ├── clicon_photo_video@2x.png │ │ ├── clicon_photo_video@3x.png │ │ ├── clicon_take_camera@2x.png │ │ ├── clicon_take_camera@3x.png │ │ ├── clicon_take_photo@2x.png │ │ ├── clicon_take_photo@3x.png │ │ ├── clicon_take_video@2x.png │ │ ├── clicon_take_video@3x.png │ │ ├── en.lproj │ │ └── Localizable.strings │ │ ├── zh-Hans.lproj │ │ └── Localizable.strings │ │ └── zh-Hant.lproj │ │ └── Localizable.strings └── Classes │ ├── .gitkeep │ ├── CLPhotoLib.h │ └── Lib │ ├── CLConfig.h │ ├── CLEditManager.h │ ├── CLEditManager.m │ ├── CLPhotoManager.h │ ├── CLPhotoManager.m │ ├── CLPickerRootController.h │ ├── CLPickerRootController.m │ ├── ext │ ├── NSBundle+CLExt.h │ ├── NSBundle+CLExt.m │ ├── UIButton+CLExt.h │ ├── UIButton+CLExt.m │ ├── UIImage+CLExt.h │ ├── UIImage+CLExt.m │ ├── UIView+CLExt.h │ └── UIView+CLExt.m │ └── others │ ├── CLAlbumTableView.h │ ├── CLAlbumTableView.m │ ├── CLEditImageController.h │ ├── CLEditImageController.m │ ├── CLEditVideoController.h │ ├── CLEditVideoController.m │ ├── CLExtHeader.h │ ├── CLPhotoCollectionCell.h │ ├── CLPhotoCollectionCell.m │ ├── CLPhotoModel.h │ ├── CLPhotoModel.m │ ├── CLPhotosViewController.h │ ├── CLPhotosViewController.m │ ├── CLPickerToolBar.h │ ├── CLPickerToolBar.m │ ├── CLPreviewCollectioCell.h │ ├── CLPreviewCollectioCell.m │ ├── CLPreviewViewController.h │ ├── CLPreviewViewController.m │ ├── CLTouchViewController.h │ └── CLTouchViewController.m ├── Example ├── CLPhotoLib.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── CLPhotoLib-Example.xcscheme ├── CLPhotoLib.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── CLPhotoLib │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── CLAppDelegate.h │ ├── CLAppDelegate.m │ ├── CLPhotoLib-Info.plist │ ├── CLPhotoLib-Prefix.pch │ ├── CLViewController.h │ ├── CLViewController.m │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── Podfile ├── Podfile.lock └── Tests │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ ├── Tests.m │ └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── README.md └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | Example/Pods/ 23 | 24 | # Bundler 25 | .bundle 26 | 27 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 28 | # Carthage/Checkouts 29 | 30 | Carthage/Build 31 | 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 35 | # 36 | # Note: if you ignore the Pods directory, make sure to uncomment 37 | # `pod install` in .travis.yml 38 | # 39 | # Pods/ 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/CLPhotoLib.xcworkspace -scheme CLPhotoLib-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /CLPhotoLib.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint CLPhotoLib.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'CLPhotoLib' 11 | s.version = '1.1.0' 12 | s.summary = '图片视频选择器<支持原图、GIF、LivePhoto>、支持图片视频编辑' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | TODO: Add long description of the pod here. 22 | DESC 23 | 24 | s.homepage = 'https://github.com/ClaudeLi/CLPhotoLib' 25 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 26 | s.license = { :type => 'MIT', :file => 'LICENSE' } 27 | s.author = { 'claudeli@yeah.net' => 'claudeli@yeah.net' } 28 | s.source = { :git => 'https://github.com/ClaudeLi/CLPhotoLib.git', :tag => s.version.to_s } 29 | # s.social_media_url = 'https://twitter.com/' 30 | 31 | s.ios.deployment_target = '8.0' 32 | 33 | s.source_files = 'CLPhotoLib/Classes/**/*.{h,m}' 34 | 35 | s.resource = 'CLPhotoLib/Assets/CLPhotoLib.bundle' 36 | # s.resource_bundles = { 37 | # 'CLPhotoLib' => ['CLPhotoLib/Assets/*.png'] 38 | # } 39 | 40 | # s.public_header_files = 'Pod/Classes/**/*.h' 41 | # s.frameworks = 'UIKit', 'MapKit' 42 | # s.dependency 'AFNetworking', '~> 2.3' 43 | s.dependency 'CLProgressFPD' 44 | end 45 | -------------------------------------------------------------------------------- /CLPhotoLib/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/.gitkeep -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSGroupSpecifier 12 | Title 13 | Group 14 | 15 | 16 | Type 17 | PSTextFieldSpecifier 18 | Title 19 | Name 20 | Key 21 | name_preference 22 | DefaultValue 23 | 24 | IsSecure 25 | 26 | KeyboardType 27 | Alphabet 28 | AutocapitalizationType 29 | None 30 | AutocorrectionType 31 | No 32 | 33 | 34 | Type 35 | PSToggleSwitchSpecifier 36 | Title 37 | Enabled 38 | Key 39 | enabled_preference 40 | DefaultValue 41 | 42 | 43 | 44 | Type 45 | PSSliderSpecifier 46 | Key 47 | slider_preference 48 | DefaultValue 49 | 0.5 50 | MinimumValue 51 | 0 52 | MaximumValue 53 | 1 54 | MinimumValueImage 55 | 56 | MaximumValueImage 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_album_more@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_album_more@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_backItem_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_backItem_icon@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_backItem_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_backItem_icon@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_original_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_original_selected@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_original_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_original_selected@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_original_unselected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_original_unselected@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_original_unselected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_original_unselected@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_photo_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_photo_selected@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_photo_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_photo_selected@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_photo_unselected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_photo_unselected@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_photo_unselected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_photo_unselected@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_preview_play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_preview_play@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/btn_preview_play@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/btn_preview_play@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_default_photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_default_photo.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_edit_left@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_edit_left@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_edit_left@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_edit_left@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_edit_right@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_edit_right@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_edit_right@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_edit_right@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_icloud_load@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_icloud_load@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_icloud_load@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_icloud_load@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_arrow@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_live@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_live@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_live@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_live@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_shadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_shadow@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_shadow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_shadow@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_video@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_video@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_video@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_photo_video@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_camera@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_camera@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_camera@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_photo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_photo@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_photo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_photo@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_video@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_video@2x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_video@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Assets/CLPhotoLib.bundle/clicon_take_video@3x.png -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "CLText_Ablum" = "Ablum"; 2 | "CLText_Cancel" = "Cancel"; 3 | "CLText_Preview" = "Preview"; 4 | "CLText_Edit" = "Edit"; 5 | "CLText_Original" = "Original"; 6 | "CLText_OK" = "OK"; 7 | "CLText_Done" = "Done"; 8 | "CLText_NotAccessAlbumTip" = "At %@ 'Settings-Privacy-Photos' Options, \r Allows %@ To Access Your Local Album."; 9 | "CLText_NotAccessCamera" = "At %@ 'Settings-Privacy-Camera' Options, \r Allows %@ To Access Your Camera."; 10 | "CLText_NotAccessMicrophone" = "At %@ 'Settings-Privacy-Microphone' Options, \r Allows %@ To Access Your Microphone."; 11 | "CLText_VideoLengthLeast" = "(Video Length At Least %@s)"; 12 | "CLText_VideoMaximumLength" = "(Video Is Longer Than %@s, Please Cut)"; 13 | "CLText_MaxImagesCount" = "You Can Select Only %ld Images"; 14 | "CLText_CannotSimulatorCamera" = "The Simulator Can't Turn On The Camera."; 15 | "CLText_SaveImageError" = "The Image Saving Failure."; 16 | "CLText_SaveVideoError" = "The Video Saving Failure."; 17 | "CLText_Processing" = "Processing"; 18 | "CLText_VideoProcessingError"="Video Processing Error"; 19 | "CLText_NotGetVideoInfo" = "Not Get Video Information"; 20 | "CLText_UnableToDecode" = "Unable To Decode"; 21 | "CLText_NotSupport" = "Temporary Don't Support"; 22 | "CLText_GotoSettings" = "Goto Settings"; 23 | -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "CLText_Ablum" = "相册"; 2 | "CLText_Cancel" = "取消"; 3 | "CLText_Preview" = "预览"; 4 | "CLText_Edit" = "编辑"; 5 | "CLText_Original" = "原图"; 6 | "CLText_OK" = "确定"; 7 | "CLText_Done" = "确定"; 8 | "CLText_NotAccessAlbumTip" = "请在%@的\"设置-隐私-照片\"选项中,\r允许%@访问你的本地相册。"; 9 | "CLText_NotAccessCamera" = "请在%@的\"设置-隐私-相机\"选项中,\r允许%@访问你的相机。"; 10 | "CLText_NotAccessMicrophone" = "请在%@的\"设置-隐私-麦克风\"选项中,\r允许%@访问你的麦克风。"; 11 | "CLText_VideoLengthLeast" = "(视频长度至少%@秒)"; 12 | "CLText_VideoMaximumLength" = "(视频长度超过%@秒,请先裁剪)"; 13 | "CLText_MaxImagesCount" = "最多只能选择%ld张图片"; 14 | "CLText_CannotSimulatorCamera" = "模拟器无法使用相机"; 15 | "CLText_SaveImageError" = "图片保存失败"; 16 | "CLText_SaveVideoError" = "视频保存失败"; 17 | "CLText_Processing" = "正在处理"; 18 | "CLText_VideoProcessingError" = "视频处理失败"; 19 | "CLText_NotGetVideoInfo" = "未获取到视频信息"; 20 | "CLText_UnableToDecode" = "无法解码"; 21 | "CLText_NotSupport" = "暂不支持"; 22 | "CLText_GotoSettings" = "去设置"; 23 | -------------------------------------------------------------------------------- /CLPhotoLib/Assets/CLPhotoLib.bundle/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "CLText_Ablum" = "相冊"; 2 | "CLText_Cancel" = "取消"; 3 | "CLText_Preview" = "預覽"; 4 | "CLText_Edit" = "編輯"; 5 | "CLText_Original" = "原圖"; 6 | "CLText_OK" = "確定"; 7 | "CLText_Done" = "確定"; 8 | "CLText_NotAccessAlbumTip" = "請在%@的\"設置-隱私-照片\"選項中,\r允許%@訪問妳的本地相冊。"; 9 | "CLText_NotAccessCamera" = "請在%@的\"設置-隱私-相機\"選項中,\r允許%@訪問妳的相機。"; 10 | "CLText_NotAccessMicrophone" = "請在%@的\"設置-隱私-麥克風\"選項中,\r允許%@訪問妳的麥克風。"; 11 | "CLText_VideoLengthLeast" = "(視頻長度至少%@秒)"; 12 | "CLText_VideoMaximumLength" = "(視頻長度超過%@秒,請先裁剪)"; 13 | "CLText_MaxImagesCount" = "最多隻能選擇%ld張圖片"; 14 | "CLText_CannotSimulatorCamera" = "模擬器無法使用相機"; 15 | "CLText_SaveImageError" = "圖片保存失敗"; 16 | "CLText_SaveVideoError" = "視頻保存失敗"; 17 | "CLText_Processing" = "正在處理"; 18 | "CLText_VideoProcessingError" = "視頻處理失敗"; 19 | "CLText_NotGetVideoInfo" = "未獲取到視頻信息"; 20 | "CLText_UnableToDecode" = "無法解碼"; 21 | "CLText_NotSupport" = "暫不支持"; 22 | "CLText_GotoSettings" = "去設置"; 23 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClaudeLi/CLPhotoLib/378212fe2c49462f2baee36ce90490eb1d558da0/CLPhotoLib/Classes/.gitkeep -------------------------------------------------------------------------------- /CLPhotoLib/Classes/CLPhotoLib.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoLib.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/23. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for CLPhotoLib. 12 | FOUNDATION_EXPORT double CLPhotoLibVersionNumber; 13 | 14 | //! Project version string for CLPhotoLib. 15 | FOUNDATION_EXPORT const unsigned char CLPhotoLibVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/CLConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLConfig.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #ifndef CLConfig_h 10 | #define CLConfig_h 11 | 12 | #ifdef DEBUG 13 | #define CLLog(format, ...) printf("\n[%s] %s [in line %d] => %s\n", __TIME__, __FUNCTION__, __LINE__, [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]); 14 | #else 15 | #define CLLog(format, ...) 16 | #endif 17 | 18 | // weak/Strong 19 | #define cl_WS(weakSelf) __weak __typeof(&*self) weakSelf = self 20 | #define cl_weakSelf(var) __weak typeof(var) weakSelf = var 21 | #define cl_strongSelf(var) __strong typeof(var) strongSelf = var 22 | #define cl_weakify(obj, weakObj) __weak typeof(obj) weakObj = obj 23 | #define cl_strongify(obj, strongObj) __strong typeof(obj) strongObj = obj 24 | 25 | #define CLColor_RGBA(r, g, b, a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a]; 26 | // Bar Item Title Color 27 | #define CLBarItemTitleDefaultColor [UIColor whiteColor] 28 | #define CLSeletedNumberColor CLColor_RGBA(86, 248, 0, 1) 29 | // Album Seleted Round Color 30 | #define CLAlbumSeletedRoundColor CLColor_RGBA(86, 248, 0, 1) 31 | #define CLAlbumSeletedNumberColor [UIColor whiteColor] 32 | 33 | // 视频填充色 34 | #define CLVideoFillColor [UIColor blackColor] 35 | // 视频输出比例 36 | static CGFloat CLVideoOutputScale = 16.0/9.0; 37 | 38 | static CGFloat CLBarAlpha = 0.98; 39 | static CGFloat CLToolBarAlpha = 0.97; 40 | static CGFloat CLBarEnabledAlpha = 0.4; 41 | static CGFloat CLShadowViewAlpha = 0.2; 42 | 43 | static CGFloat CLNavigationItemFontSize = 16.0; 44 | static CGFloat CLToolBarTitleFontSize = 16.0; 45 | static CGFloat CLToolBarHeight = 42.0; 46 | 47 | static CGFloat CLAlbumDropDownScale = 0.7; 48 | 49 | static CGFloat CLAlbumDropDownAnimationTime = 0.3; 50 | static CGFloat CLAlbumPackUpAnimationTime = 0.2; 51 | static CGFloat CLLittleControlAnimationTime = 0.2; 52 | 53 | static inline CGFloat CLAlbumRowHeight(void) { 54 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { 55 | return 90.0f; 56 | } else { 57 | return 70.0f; 58 | } 59 | } 60 | 61 | // 视频输出路径 62 | static inline NSString *CLVideoOutputPath(void) { 63 | NSTimeInterval time = [[NSDate date] timeIntervalSince1970]; 64 | NSString *str = [NSString stringWithFormat:@"%.0f.mp4", time]; 65 | return [NSTemporaryDirectory() stringByAppendingPathComponent:str]; 66 | } 67 | 68 | static inline CGFloat GetMatchValue(NSString *text, CGFloat fontSize, BOOL isHeightFixed, CGFloat fixedValue) { 69 | CGSize size; 70 | if (isHeightFixed) { 71 | size = CGSizeMake(MAXFLOAT, fixedValue); 72 | } else { 73 | size = CGSizeMake(fixedValue, MAXFLOAT); 74 | } 75 | CGSize resultSize; 76 | if ([[[UIDevice currentDevice] systemVersion] doubleValue] >= 7.0) { 77 | //返回计算出的size 78 | resultSize = [text boundingRectWithSize:size options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:fontSize]} context:nil].size; 79 | } 80 | if (isHeightFixed) { 81 | return resultSize.width; 82 | } else { 83 | return resultSize.height; 84 | } 85 | } 86 | 87 | #endif /* CLConfig_h */ 88 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/CLEditManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLEditManager.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/16. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | typedef NS_ENUM(NSInteger, CLVideoCutMode) { 14 | CLVideoCutModeScaleAspectFit = 0, // 填充模式 15 | CLVideoCutModeScaleAspectFill, // 居中裁剪 16 | }; 17 | 18 | @class CLEditManager; 19 | @protocol CLVideoProcessingDelegate 20 | 21 | @required 22 | 23 | @optional 24 | 25 | - (void)editManager:(CLEditManager *)editManager didFinishedOutputURL:(NSURL *)outputURL; 26 | - (void)editManager:(CLEditManager *)editManager operationFailure:(NSError *)error; 27 | - (void)editManager:(CLEditManager *)editManager handlingProgress:(CGFloat)progress; 28 | 29 | @end 30 | 31 | @interface CLEditManager : NSObject 32 | 33 | @property (nonatomic, assign) iddelegate; 34 | /** 35 | 视频处理 36 | 37 | @param asset video asset 38 | @param range 视频时长裁剪范围 39 | @param sizeScale 输出比例 40 | @param degrees 旋转角度 41 | @param cutMode 裁剪模式 42 | @param fillColor 填充色 43 | */ 44 | - (void)exportEditVideoForAsset:(AVAsset *)asset 45 | range:(CMTimeRange)range 46 | sizeScale:(CGFloat)sizeScale 47 | degrees:(CGFloat)degrees 48 | cutMode:(CLVideoCutMode)cutMode 49 | fillColor:(UIColor *)fillColor 50 | presetName:(NSString *)presetName; 51 | 52 | /** 53 | 视频处理 54 | 55 | @param asset video asset 56 | @param range 视频时长裁剪范围 57 | @param sizeScale 输出比例 58 | @param degrees 旋转角度 59 | @param isDistinguishWH 是否区分横竖比 60 | @param cutMode 裁剪模式 61 | @param fillColor 填充色 62 | */ 63 | - (void)exportEditVideoForAsset:(AVAsset *)asset 64 | range:(CMTimeRange)range 65 | sizeScale:(CGFloat)sizeScale 66 | degrees:(CGFloat)degrees 67 | isDistinguishWH:(BOOL)isDistinguishWH 68 | cutMode:(CLVideoCutMode)cutMode 69 | fillColor:(UIColor *)fillColor 70 | presetName:(NSString *)presetName; 71 | 72 | // 取消处理 73 | - (void)cancelExport; 74 | 75 | #pragma mark - 76 | #pragma mark -- Class Methods -- 77 | /** 78 | 获取某时刻缩略图 79 | 80 | @param asset videoAsset 81 | @param timeBySecond 时间,单位:秒 82 | @return 缩略图 83 | */ 84 | + (UIImage *)requestThumbnailImageForAVAsset:(AVAsset *)asset 85 | timeBySecond:(NSTimeInterval)timeBySecond; 86 | 87 | /** 88 | 获取缩略图数组 89 | */ 90 | + (void)requestThumbnailImagesForAVAsset:(AVAsset *)asset 91 | interval:(NSTimeInterval)interval 92 | size:(CGSize)size 93 | eachThumbnail:(void (^)(UIImage *image))eachThumbnail 94 | complete:(void (^)(AVAsset *asset, NSArray *images))complete; 95 | 96 | /** 97 | 获取缩略图数组 98 | */ 99 | + (void)requestThumbnailImagesForAVAsset:(AVAsset *)asset 100 | duration:(NSTimeInterval)duration 101 | imageCount:(NSInteger)imageCount 102 | interval:(NSTimeInterval)interval 103 | size:(CGSize)size 104 | eachThumbnail:(void (^)(UIImage *image))eachThumbnail 105 | complete:(void (^)(AVAsset *asset, NSArray *images))complete; 106 | 107 | + (void)cancelAllCGImageGeneration; 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/CLPhotoManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoManager.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/10/31. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "CLPickerRootController.h" 12 | 13 | #define CLPhotoShareManager [CLPhotoManager shareManager] 14 | #define CLAppName CLPhotoShareManager.appName 15 | #define CLMinSize CLPhotoShareManager.minSize 16 | #define CLAllowSelectGif CLPhotoShareManager.allowSelectGif 17 | #define CLAllowSelectLivePhoto CLPhotoShareManager.allowSelectLivePhoto 18 | #define CLSortAscending CLPhotoShareManager.sortAscending 19 | 20 | @class CLAlbumModel; 21 | @class CLPhotoModel; 22 | @interface CLPhotoManager : NSObject 23 | 24 | + (instancetype)shareManager; 25 | 26 | @property (nonatomic, copy) NSString *appName; 27 | 28 | @property (nonatomic, assign) CGSize minSize; 29 | @property (nonatomic, assign) BOOL allowSelectGif; 30 | @property (nonatomic, assign) BOOL allowSelectLivePhoto; 31 | @property (nonatomic, assign) BOOL sortAscending; 32 | 33 | #pragma mark -- Object Methods -- 34 | // 获得(相册交卷/所有图片)所在的相册 35 | - (void)getCameraRollAlbumWithSelectMode:(CLPickerSelectMode)selectMode complete:(void (^)(CLAlbumModel *albumModel))complete; 36 | 37 | // 获得 所有相册/相册数组 38 | - (void)getAlbumListWithSelectMode:(CLPickerSelectMode)selectMode completion:(void (^)(NSArray *models))completion; 39 | 40 | // 获取选中图片数组 41 | - (void)requestImagesWithModelArray:(NSMutableArray *)modelArray isOriginal:(BOOL)isOriginal completion:(void (^)(NSArray *photos, NSArray *assets))completion; 42 | 43 | // 获取LivePhoto 44 | - (void)requestLivePhotoForAsset:(PHAsset *)asset completion:(void (^)(PHLivePhoto *livePhoto, NSDictionary *info))completion API_AVAILABLE(ios(9.1)); 45 | 46 | // 获取图片data 47 | - (void)requestOriginalImageDataForAsset:(PHAsset *)asset completion:(void (^)(NSData *data, NSDictionary *info))completion; 48 | 49 | // 获取视频AVPlayerItem 50 | - (void)requestVideoPlayerItemForAsset:(PHAsset *)asset completion:(void (^)(AVPlayerItem *item, NSDictionary *info))completion; 51 | 52 | // 获取视频AVAsset 53 | - (void)requestVideoAssetForAsset:(PHAsset *)asset completion:(void (^)(AVAsset *asset, NSDictionary *info))completion; 54 | 55 | // 获取原图 56 | - (PHImageRequestID)requestOriginalImageForAsset:(PHAsset *)asset completion:(void (^)(UIImage *image, NSDictionary *))completion; 57 | 58 | // 获取自定义size的图片 59 | - (PHImageRequestID)requestCustomImageForAsset:(PHAsset *)asset size:(CGSize)size completion:(void (^)(UIImage *image, NSDictionary *info))completion; 60 | 61 | // 获取size&resizeMode的图片 62 | - (PHImageRequestID)requestImageForAsset:(PHAsset *)asset size:(CGSize)size resizeMode:(PHImageRequestOptionsResizeMode)resizeMode completion:(void (^)(UIImage *image, NSDictionary *info))completion; 63 | 64 | #pragma mark - 65 | #pragma mark -- Public Class Methods -- 66 | // gif data转图片 67 | + (UIImage *)transformToGifImageWithData:(NSData *)data; 68 | 69 | // 获取Photos大小 70 | + (void)getPhotosBytesWithArray:(NSArray *)photos completion:(void (^)(NSString *photosBytes))completion; 71 | 72 | // 获取PHAsset.localIdentifier数组 73 | + (NSArray *)getLocalIdentifierArrayWithArray:(NSArray *)array; 74 | 75 | // 检测是否选中 76 | + (BOOL)checkSelcectedWithModel:(CLPhotoModel *)model identifiers:(NSArray *)identifiers; 77 | 78 | // 检测数组中包含的选中图片 79 | + (NSInteger)checkSelcectModelInArray:(NSArray *)dataArray selArray:(NSArray *)selArray; 80 | 81 | // 保存图片到相册 82 | + (void)saveImageToAblum:(UIImage *)image completion:(void (^)(BOOL success, PHAsset *asset))completion; 83 | 84 | // 保存视频到相册 85 | + (void)saveVideoToAblum:(NSURL *)url completion:(void (^)(BOOL success, PHAsset *asset))completion; 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/CLPickerRootController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPickerRootController.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** 13 | 选择模式 14 | - CLPickerSelectModeMixDisplay: 混合显示 15 | - CLPickerSelectModeAllowImage: 只显示图片 16 | - CLPickerSelectModeAllowVideo: 只显示视频 17 | */ 18 | typedef NS_ENUM(NSInteger, CLPickerSelectMode) { 19 | CLPickerSelectModeMixDisplay, 20 | CLPickerSelectModeAllowImage, 21 | CLPickerSelectModeAllowVideo, 22 | }; 23 | 24 | typedef void(^CLPickingPhotosHandle)(NSArray *photos, NSArray *assets); 25 | typedef void(^CLPickingVideoHandle)(UIImage *videoCover, NSURL *videoURL); 26 | typedef void(^CLPickerCancelHandle)(void); 27 | typedef void(^CLPickerShootVideoHandle)(void); 28 | 29 | @class CLPhotoModel; 30 | @protocol CLPickerRootControllerDelegate; 31 | @interface CLPickerRootController : UINavigationController 32 | 33 | /** 34 | 启动选择器前状态栏样式 默认UIStatusBarStyleLightContent 35 | previous status bar style defatlt UIStatusBarStyleLightContent 36 | */ 37 | @property (nonatomic, assign) UIStatusBarStyle previousStatusBarStyle; 38 | 39 | /** 40 | 默认 白色状态栏 41 | default UIStatusBarStyleLightContent 42 | */ 43 | @property (nonatomic, assign) UIStatusBarStyle statusBarStyle; 44 | 45 | /** 46 | 是否允许旋转 47 | default NO 48 | */ 49 | @property (nonatomic, assign) BOOL allowAutorotate; 50 | 51 | /** 52 | 选择器背景色 默认白色 53 | background color default whiteColor 54 | */ 55 | @property (nonatomic) UIColor *backgroundColor; 56 | 57 | /** 58 | 导航栏背景色 默认 darkGrayColor 59 | navigation color default darkGrayColor 60 | */ 61 | @property (nonatomic) UIColor *navigationColor; 62 | 63 | /** 64 | 导航栏背景图 65 | */ 66 | @property (nonatomic) UIImage *navigationBarImage; 67 | 68 | /** 69 | 标题颜色 默认白色 70 | title color default whiteColor 71 | */ 72 | @property (nonatomic) UIColor *titleColor; 73 | 74 | /** 75 | 导航栏按钮字体颜色 默认白色 76 | navigation item color default CLBarItemTitleDefaultColor 77 | */ 78 | @property (nonatomic) UIColor *navigationItemColor; 79 | 80 | /** 81 | 底部控制栏背景色 默认和导航栏背景色相同 82 | bottom tool bar backgroundColor default same navigationColor 83 | */ 84 | @property (nonatomic) UIColor *toolBarBackgroundColor; 85 | 86 | /** 87 | 底部控制栏按钮颜色 默认和导航栏按钮颜色相同 88 | bottom tool bar item color default same navigationItemColor 89 | */ 90 | @property (nonatomic) UIColor *toolBarItemColor; 91 | 92 | #pragma mark - 93 | /** 94 | 选择图片较小的一边 不超过(750, 750) 95 | select image min size, Default is (750, 750) 96 | */ 97 | @property (nonatomic, assign) CGSize minSize; 98 | 99 | @property (nonatomic, assign) NSInteger columnCount; // default ipad:5 else 3 100 | @property (nonatomic, assign) CGFloat minimumInteritemSpacing; // default 1.0 101 | @property (nonatomic, assign) CGFloat minimumLineSpacing; // default 1.0 102 | @property (nonatomic, assign) UIEdgeInsets sectionInset; // default UIEdgeInsetsMake(1, 0, 1, 0) 103 | 104 | @property (nonatomic, assign) BOOL allowImgMultiple; // default YES 105 | @property (nonatomic, assign) NSInteger maxSelectCount; // image max default 9 106 | @property (nonatomic, assign) CGFloat minDuration; // default 0.0 107 | @property (nonatomic, assign) CGFloat maxDuration; // default MAXFLOAT 108 | @property (nonatomic, assign) NSString *presetName; // AVAssetExportPresetMediumQuality 109 | @property (nonatomic, assign) CGFloat outputVideoScale; // default 16/9, 注:0为不处理比例 110 | @property (nonatomic, assign) BOOL isDistinguishWH; // 是否区分视频宽高比(outputScale宽高比是否可以互换), default NO 111 | @property (nonatomic, assign) BOOL allowEditVideo; // default YES 112 | 113 | @property (nonatomic, assign) BOOL allowAlbumDropDown; // default NO (是否下拉选择相册) 114 | @property (nonatomic, assign) BOOL allowPanGestureSelect; // default YES 115 | @property (nonatomic, assign) BOOL allowPreviewImage; // default YES 116 | @property (nonatomic, assign) BOOL allowEditImage; // default NO 117 | @property (nonatomic, assign) BOOL allowSelectOriginalImage; // default NO 118 | @property (nonatomic, assign) BOOL allowDoneOnToolBar; // default YES 119 | 120 | @property (nonatomic, assign) BOOL allowSelectGif; // default YES 121 | @property (nonatomic, assign) BOOL allowSelectLivePhoto; // default YES 122 | @property (nonatomic, assign) BOOL allowTakePhoto; // default YES 123 | @property (nonatomic, assign) BOOL sortAscending; // default NO 124 | @property (nonatomic, assign) BOOL showCaptureOnCell; // default NO 125 | @property (nonatomic, assign) BOOL usedCustomRecording; // default NO 126 | @property (nonatomic, assign) CLPickerSelectMode selectMode; // default CLPickerSelectModeMixDisplay 127 | @property (nonatomic, strong) NSArray *selectedAssets; 128 | 129 | #pragma mark -- Delegate | Block -- 130 | @property (nonatomic, weak) idpickerDelegate; 131 | @property (nonatomic, copy) CLPickingPhotosHandle pickingPhotosHandle; 132 | @property (nonatomic, copy) CLPickingVideoHandle pickingVideoHandle; 133 | @property (nonatomic, copy) CLPickerCancelHandle pickerCancelHandle; 134 | @property (nonatomic, copy) CLPickerShootVideoHandle pickerShootVideoHandle; 135 | 136 | @property (nonatomic, strong) NSMutableArray *selectedModels; 137 | @property (nonatomic, assign) BOOL selectedOriginalImage; 138 | 139 | - (void)clickCancelAction; 140 | - (void)clickShootVideoAction; 141 | - (void)didFinishPickingPhotosAction; 142 | - (void)clickPickingVideoActionForAsset:(AVAsset *)asset 143 | range:(CMTimeRange)range 144 | degrees:(CGFloat)degrees; 145 | - (void)cancelExport; 146 | 147 | - (void)showText:(NSString *)text; 148 | - (void)showText:(NSString *)text delay:(NSTimeInterval)delay; 149 | - (void)showProgress; 150 | - (void)showProgressWithText:(NSString *)text; 151 | - (void)hideProgress; 152 | 153 | @end 154 | 155 | @protocol CLPickerRootControllerDelegate 156 | 157 | @optional 158 | 159 | // images 160 | - (void)clPickerController:(CLPickerRootController *)picker didFinishPickingPhotos:(NSArray *)photos assets:(NSArray *)assets; 161 | 162 | // video 163 | - (void)clPickerController:(CLPickerRootController *)picker didFinishPickingVideoCover:(UIImage *)videoCover videoURL:(NSURL *)videoURL; 164 | 165 | // Cancel Picker 166 | - (void)clPickerControllerDidCancel:(CLPickerRootController *)picker; 167 | 168 | // User can write Custom video camera. 169 | - (void)clPickerControllerDidShootVideo:(CLPickerRootController *)picker; 170 | 171 | @end 172 | 173 | @interface CLAlbumPickerController : UIViewController 174 | 175 | @end 176 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/CLPickerRootController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLPickerRootController.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLPickerRootController.h" 10 | #import "CLPhotosViewController.h" 11 | #import "CLConfig.h" 12 | #import 13 | #import "CLExtHeader.h" 14 | 15 | @interface CLPickerRootController () { 16 | NSTimer *_timer; 17 | BOOL _canRotate; 18 | } 19 | 20 | @property (nonatomic, strong) UILabel *tipLable; 21 | 22 | @property (nonatomic, strong) CLProgressFPD *progressHUD; 23 | 24 | @property (nonatomic, strong) CLEditManager *editManager; 25 | 26 | @end 27 | 28 | @implementation CLPickerRootController 29 | 30 | - (instancetype)init { 31 | CLAlbumPickerController *albumPickerVc = [[CLAlbumPickerController alloc] init]; 32 | self = [super initWithRootViewController:albumPickerVc]; 33 | if (self) { 34 | self.backgroundColor = _backgroundColor?:[UIColor whiteColor]; 35 | self.titleColor = _titleColor?:[UIColor whiteColor]; 36 | self.navigationItemColor = _navigationItemColor?:CLBarItemTitleDefaultColor; 37 | self.minSize = CGSizeMake(750.0f, 750.0f); 38 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { 39 | self.columnCount = 5; 40 | } else { 41 | self.columnCount = 3; 42 | } 43 | self.minimumLineSpacing = 1.0f; 44 | self.minimumInteritemSpacing = 1.0f; 45 | self.sectionInset = UIEdgeInsetsMake(1.0f, 0, 1.0f, 0); 46 | 47 | self.maxSelectCount = 9; 48 | self.maxDuration = MAXFLOAT; 49 | self.outputVideoScale = CLVideoOutputScale; 50 | self.allowEditVideo = YES; 51 | self.allowAlbumDropDown = NO; 52 | self.allowPanGestureSelect = YES; 53 | self.allowImgMultiple = YES; 54 | self.presetName = AVAssetExportPresetMediumQuality; 55 | 56 | self.allowPreviewImage = YES; 57 | self.allowEditImage = NO; 58 | self.allowSelectOriginalImage = NO; 59 | self.allowDoneOnToolBar = YES; 60 | 61 | self.allowSelectGif = YES; 62 | self.allowSelectLivePhoto = YES; 63 | self.allowTakePhoto = YES; 64 | self.sortAscending = NO; 65 | self.showCaptureOnCell = NO; 66 | 67 | self.selectMode = CLPickerSelectModeMixDisplay; 68 | 69 | if ([PHPhotoLibrary authorizationStatus] != PHAuthorizationStatusAuthorized) { 70 | self.tipLable.hidden = NO; 71 | _timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(observeAuthrizationStatusChange) userInfo:nil repeats:YES]; 72 | } else { 73 | [self gotoPhotosViewController]; 74 | } 75 | } 76 | return self; 77 | } 78 | 79 | - (void)viewDidLoad { 80 | [super viewDidLoad]; 81 | self.previousStatusBarStyle = UIStatusBarStyleLightContent; 82 | self.statusBarStyle = UIStatusBarStyleLightContent; 83 | self.navigationColor = [UIColor darkGrayColor]; 84 | self.navigationBar.translucent = YES; 85 | } 86 | 87 | - (UIStatusBarStyle)preferredStatusBarStyle { 88 | return UIStatusBarStyleLightContent; 89 | } 90 | 91 | - (void)viewWillDisappear:(BOOL)animated { 92 | [super viewWillDisappear:animated]; 93 | [self clearTimer]; 94 | [UIApplication sharedApplication].statusBarStyle = self.previousStatusBarStyle; 95 | } 96 | 97 | - (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle { 98 | _statusBarStyle = statusBarStyle; 99 | if (_navigationColor) { 100 | self.navigationBar.barStyle = _statusBarStyle?UIBarStyleBlack:UIBarStyleDefault; 101 | } 102 | [UIApplication sharedApplication].statusBarStyle = _statusBarStyle; 103 | } 104 | 105 | - (void)setNavigationColor:(UIColor *)navigationColor { 106 | _navigationColor = navigationColor; 107 | [self.navigationBar setBackgroundImage:[UIImage imageWithColor:_navigationColor] forBarMetrics:UIBarMetricsDefault]; 108 | [self.navigationBar setShadowImage:[UIImage new]]; 109 | } 110 | 111 | - (void)setNavigationBarImage:(UIImage *)navigationBarImage { 112 | [self.navigationBar setBackgroundImage:navigationBarImage forBarMetrics:UIBarMetricsDefault]; 113 | [self.navigationBar setShadowImage:[UIImage new]]; 114 | } 115 | 116 | - (void)setNavigationItemColor:(UIColor *)navigationItemColor { 117 | _navigationItemColor = navigationItemColor; 118 | self.navigationBar.tintColor = _navigationItemColor; 119 | } 120 | 121 | - (void)setTitleColor:(UIColor *)titleColor { 122 | _titleColor = titleColor; 123 | self.navigationBar.titleTextAttributes = [NSDictionary dictionaryWithObject:_titleColor forKey:NSForegroundColorAttributeName]; 124 | } 125 | 126 | - (void)setMinSize:(CGSize)minSize { 127 | _minSize = minSize; 128 | CLMinSize = minSize; 129 | } 130 | 131 | - (void)setAllowSelectGif:(BOOL)allowSelectGif { 132 | _allowSelectGif = allowSelectGif; 133 | CLAllowSelectGif = _allowSelectGif; 134 | } 135 | 136 | - (void)setAllowSelectLivePhoto:(BOOL)allowSelectLivePhoto { 137 | _allowSelectLivePhoto = allowSelectLivePhoto; 138 | CLAllowSelectLivePhoto = _allowSelectLivePhoto; 139 | } 140 | 141 | - (void)setSortAscending:(BOOL)sortAscending { 142 | _sortAscending = sortAscending; 143 | CLSortAscending = _sortAscending; 144 | } 145 | - (void)setSelectedAssets:(NSArray *)selectedAssets { 146 | _selectedAssets = selectedAssets; 147 | for (id asset in _selectedAssets) { 148 | CLPhotoModel *model = [CLPhotoModel modelWithAsset:asset]; 149 | model.isSelected = YES; 150 | [self.selectedModels addObject:model]; 151 | } 152 | } 153 | 154 | - (NSMutableArray *)selectedModels { 155 | if (!_selectedModels) { 156 | _selectedModels = [NSMutableArray array]; 157 | } 158 | return _selectedModels; 159 | } 160 | 161 | -(BOOL)shouldAutorotate { 162 | return self.allowAutorotate; 163 | } 164 | 165 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations { 166 | if (self.allowAutorotate) { 167 | return UIInterfaceOrientationMaskAll; 168 | } else { 169 | return UIInterfaceOrientationMaskPortrait; 170 | } 171 | } 172 | 173 | -(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { 174 | return [self.viewControllers.lastObject preferredInterfaceOrientationForPresentation]; 175 | } 176 | 177 | - (void)observeAuthrizationStatusChange { 178 | if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) { 179 | [self gotoPhotosViewController]; 180 | [self clearTimer]; 181 | } 182 | } 183 | 184 | - (void)clearTimer { 185 | if (_timer) { 186 | [_tipLable removeFromSuperview]; 187 | _tipLable = nil; 188 | [_timer invalidate]; 189 | _timer = nil; 190 | } 191 | } 192 | 193 | - (void)gotoPhotosViewController { 194 | CLPhotosViewController *imagePicker = [[CLPhotosViewController alloc] init]; 195 | [self showViewController:imagePicker sender:nil]; 196 | } 197 | 198 | #pragma mark - 199 | #pragma mark -- Lazy Loads -- 200 | - (UILabel *)tipLable { 201 | if (!_tipLable) { 202 | _tipLable = [[UILabel alloc] init]; 203 | _tipLable.textAlignment = NSTextAlignmentCenter; 204 | _tipLable.numberOfLines = 0; 205 | _tipLable.font = [UIFont systemFontOfSize:16]; 206 | _tipLable.textColor = [UIColor blackColor]; 207 | _tipLable.adjustsFontSizeToFitWidth = YES; 208 | _tipLable.text = [NSString stringWithFormat:CLString(@"CLText_NotAccessAlbumTip"), [UIDevice currentDevice].model, CLAppName]; 209 | [self.view addSubview:_tipLable]; 210 | } 211 | return _tipLable; 212 | } 213 | 214 | - (CLProgressFPD *)progressHUD { 215 | if (!_progressHUD) { 216 | _progressHUD = [[CLProgressFPD alloc] init]; 217 | [self.view addSubview:_progressHUD]; 218 | } 219 | return _progressHUD; 220 | } 221 | 222 | - (CLEditManager *)editManager { 223 | if (!_editManager) { 224 | _editManager = [[CLEditManager alloc] init]; 225 | _editManager.delegate = self; 226 | } 227 | return _editManager; 228 | } 229 | 230 | - (void)viewWillLayoutSubviews { 231 | [super viewWillLayoutSubviews]; 232 | _tipLable.frame = CGRectMake(15, self.view.height/4.0, self.view.width - 30, 60); 233 | } 234 | 235 | #pragma mark - 236 | #pragma mark -- Public Methods -- 237 | - (void)showText:(NSString *)text { 238 | [self.progressHUD showText:text]; 239 | } 240 | 241 | - (void)showText:(NSString *)text delay:(NSTimeInterval)delay { 242 | [self.progressHUD showText:text delay:delay]; 243 | } 244 | 245 | - (void)showProgress { 246 | [self.progressHUD showProgress:NO]; 247 | } 248 | 249 | - (void)showProgressWithText:(NSString *)text { 250 | [self.progressHUD showProgressWithText:text]; 251 | } 252 | 253 | - (void)hideProgress { 254 | [self.progressHUD hideProgress]; 255 | } 256 | 257 | - (void)clickCancelAction { 258 | cl_weakSelf(self); 259 | [self dismissViewControllerAnimated:YES completion:^{ 260 | cl_strongSelf(weakSelf); 261 | if (!strongSelf) { 262 | return; 263 | } 264 | if ([strongSelf.pickerDelegate respondsToSelector:@selector(clPickerControllerDidCancel:)]) { 265 | [strongSelf.pickerDelegate clPickerControllerDidCancel:strongSelf]; 266 | } 267 | if (strongSelf.pickerCancelHandle) { 268 | strongSelf.pickerCancelHandle(); 269 | } 270 | }]; 271 | } 272 | 273 | - (void)clickShootVideoAction { 274 | cl_weakSelf(self); 275 | [self dismissViewControllerAnimated:YES completion:^{ 276 | cl_strongSelf(weakSelf); 277 | if (!strongSelf) { 278 | return; 279 | } 280 | if ([strongSelf.pickerDelegate respondsToSelector:@selector(clPickerControllerDidShootVideo:)]) { 281 | [strongSelf.pickerDelegate clPickerControllerDidShootVideo:strongSelf]; 282 | } 283 | if (strongSelf.pickerShootVideoHandle) { 284 | strongSelf.pickerShootVideoHandle(); 285 | } 286 | }]; 287 | } 288 | 289 | - (void)didFinishPickingPhotosAction { 290 | [self showProgress]; 291 | cl_weakSelf(self); 292 | [CLPhotoShareManager requestImagesWithModelArray:self.selectedModels isOriginal:self.selectedOriginalImage completion:^(NSArray *photos, NSArray *assets) { 293 | cl_strongSelf(weakSelf); 294 | if (!strongSelf) { 295 | return; 296 | } 297 | [strongSelf hideProgress]; 298 | [strongSelf dismissViewControllerAnimated:YES completion:^{ 299 | if ([strongSelf.pickerDelegate respondsToSelector:@selector(clPickerController:didFinishPickingPhotos:assets:)]) { 300 | [strongSelf.pickerDelegate clPickerController:strongSelf didFinishPickingPhotos:photos assets:assets]; 301 | } 302 | if (strongSelf.pickingPhotosHandle) { 303 | strongSelf.pickingPhotosHandle(photos, assets); 304 | } 305 | }]; 306 | }]; 307 | } 308 | 309 | - (void)clickPickingVideoActionForAsset:(AVAsset *)asset range:(CMTimeRange)range degrees:(CGFloat)degrees { 310 | _canRotate = self.allowAutorotate; 311 | self.allowAutorotate = NO; 312 | [self.editManager exportEditVideoForAsset:asset 313 | range:range 314 | sizeScale:self.outputVideoScale 315 | degrees:degrees 316 | isDistinguishWH:self.isDistinguishWH 317 | cutMode:CLVideoCutModeScaleAspectFit 318 | fillColor:CLVideoFillColor 319 | presetName:self.presetName]; 320 | } 321 | 322 | - (void)cancelExport { 323 | if (_editManager) { 324 | [_editManager cancelExport]; 325 | } 326 | } 327 | 328 | - (void)didFinishPickingVideoCover:(UIImage *)videoCover videoURL:(NSURL *)videoURL { 329 | cl_weakSelf(self); 330 | [self dismissViewControllerAnimated:YES completion:^{ 331 | cl_strongSelf(weakSelf); 332 | if (!strongSelf) { 333 | return; 334 | } 335 | if ([strongSelf.pickerDelegate respondsToSelector:@selector(clPickerController:didFinishPickingVideoCover:videoURL:)]) { 336 | [strongSelf.pickerDelegate clPickerController:strongSelf didFinishPickingVideoCover:videoCover videoURL:videoURL]; 337 | } 338 | if (strongSelf.pickingVideoHandle) { 339 | strongSelf.pickingVideoHandle(videoCover, videoURL); 340 | } 341 | }]; 342 | } 343 | 344 | #pragma mark - 345 | #pragma mark -- CLVideoProcessingDelegate -- 346 | - (void)editManager:(CLEditManager *)editManager didFinishedOutputURL:(NSURL *)outputURL { 347 | UIImage *cover = [CLEditManager requestThumbnailImageForAVAsset:[AVAsset assetWithURL:outputURL] timeBySecond:0]; 348 | [self hideProgress]; 349 | if (_canRotate) { 350 | self.allowAutorotate = _canRotate; 351 | } 352 | [self didFinishPickingVideoCover:cover videoURL:outputURL]; 353 | /* 354 | [CLPhotoManager saveVideoToAblum:outputURL completion:^(BOOL success, PHAsset *asset) { 355 | if (success) { 356 | CLLog(@"保存到本地相册"); 357 | } 358 | }]; 359 | */ 360 | } 361 | 362 | -(void)editManager:(CLEditManager *)editManager handlingProgress:(CGFloat)progress { 363 | [self showProgressWithText:[NSString stringWithFormat:@"%@ %.0f%%", CLString(@"CLText_Processing"), (progress * 100.0)]]; 364 | } 365 | 366 | -(void)editManager:(CLEditManager *)editManager operationFailure:(NSError *)error { 367 | if (_canRotate) { 368 | self.allowAutorotate = _canRotate; 369 | } 370 | [self showText:error.localizedDescription]; 371 | } 372 | 373 | -(void)dealloc { 374 | CLLog(@"%s", __func__); 375 | } 376 | 377 | - (void)didReceiveMemoryWarning { 378 | [super didReceiveMemoryWarning]; 379 | } 380 | 381 | @end 382 | 383 | #import "CLAlbumTableView.h" 384 | #import "CLPhotoModel.h" 385 | @interface CLAlbumPickerController () { 386 | BOOL _reload; 387 | } 388 | 389 | @property (nonatomic, strong) CLAlbumTableView *tableView; 390 | 391 | @end 392 | 393 | @implementation CLAlbumPickerController 394 | 395 | - (CLAlbumTableView *)tableView { 396 | if (!_tableView) { 397 | _tableView = [[CLAlbumTableView alloc] init]; 398 | _tableView.backgroundColor = [UIColor whiteColor]; 399 | _tableView.tableColor = [UIColor whiteColor]; 400 | [self.view addSubview:_tableView]; 401 | } 402 | return _tableView; 403 | } 404 | 405 | - (CLPickerRootController *)picker { 406 | return (CLPickerRootController *)self.navigationController; 407 | } 408 | 409 | - (void)viewDidLoad { 410 | [super viewDidLoad]; 411 | self.edgesForExtendedLayout = UIRectEdgeTop; 412 | self.view.backgroundColor = [UIColor whiteColor]; 413 | self.title = CLString(@"CLText_Ablum"); 414 | self.tableView.hidden = NO; 415 | cl_WS(ws); 416 | [_tableView setDidSelectAlbumBlock:^(CLAlbumModel *model) { 417 | [ws pushImagePickerWithModel:model]; 418 | }]; 419 | [self _initRightItem]; 420 | if (![NSBundle clLocalizedBundle]) { 421 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:@"Not Found\n CLPhotoLib.bundle" preferredStyle:UIAlertControllerStyleAlert]; 422 | UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; 423 | [alert addAction:action]; 424 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { 425 | UIPopoverPresentationController *popPresenter = [alert popoverPresentationController]; 426 | popPresenter.sourceView = self.view; 427 | popPresenter.sourceRect = self.view.bounds; 428 | [self presentViewController:alert animated:YES completion:nil]; 429 | } else { 430 | [self presentViewController:alert animated:YES completion:nil]; 431 | } 432 | } 433 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadAlbumList) name:CLPhotoLibReloadAlbumList object:nil]; 434 | } 435 | 436 | - (void)reloadAlbumList { 437 | _reload = YES; 438 | } 439 | 440 | - (void)viewWillLayoutSubviews { 441 | [super viewWillLayoutSubviews]; 442 | [UIApplication sharedApplication].statusBarHidden = self.navigationController.navigationBar.hidden; 443 | _tableView.frame = self.view.bounds; 444 | [_tableView showAlbumAnimated:NO]; 445 | } 446 | 447 | - (void)viewWillAppear:(BOOL)animated { 448 | [super viewWillAppear:animated]; 449 | if (_reload || !_tableView.albumArray) { 450 | _reload = NO; 451 | CLPickerRootController *pk = self.picker; 452 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 453 | cl_weakSelf(self); 454 | [CLPhotoShareManager getAlbumListWithSelectMode:pk.selectMode completion:^(NSArray *models) { 455 | cl_strongSelf(weakSelf); 456 | if (strongSelf) { 457 | dispatch_async(dispatch_get_main_queue(), ^{ 458 | for (CLAlbumModel *albumModel in models) { 459 | albumModel.selectedModels = strongSelf.picker.selectedModels; 460 | } 461 | strongSelf.tableView.albumArray = models; 462 | }); 463 | } 464 | }]; 465 | }); 466 | } else { 467 | for (CLAlbumModel *albumModel in _tableView.albumArray) { 468 | albumModel.selectedModels = self.picker.selectedModels; 469 | } 470 | [_tableView reloadData]; 471 | } 472 | } 473 | 474 | - (void)_initRightItem { 475 | UIButton *rightItem = [UIButton buttonWithType:UIButtonTypeCustom]; 476 | rightItem.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 477 | CGFloat width = GetMatchValue(CLString(@"CLText_Cancel"), CLNavigationItemFontSize, YES, self.navigationController.navigationBar.height); 478 | rightItem.frame = CGRectMake(0, 0, width, self.navigationController.navigationBar.height); 479 | rightItem.titleLabel.font = [UIFont systemFontOfSize:CLNavigationItemFontSize]; 480 | [rightItem setTitle:CLString(@"CLText_Cancel") forState:UIControlStateNormal]; 481 | rightItem.titleLabel.adjustsFontSizeToFitWidth = YES; 482 | [rightItem setTitleColor:self.picker.navigationItemColor forState:UIControlStateNormal]; 483 | [rightItem addTarget:self action:@selector(clickRightItemAction) forControlEvents:UIControlEventTouchUpInside]; 484 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:rightItem]; 485 | } 486 | 487 | - (void)clickRightItemAction { 488 | [self.picker clickCancelAction]; 489 | } 490 | 491 | - (void)pushImagePickerWithModel:(CLAlbumModel *)model { 492 | CLPhotosViewController *imagePicker = [[CLPhotosViewController alloc] init]; 493 | imagePicker.albumModel = model; 494 | [self.navigationController showViewController:imagePicker sender:self]; 495 | } 496 | 497 | - (void)didReceiveMemoryWarning { 498 | [super didReceiveMemoryWarning]; 499 | // Dispose of any resources that can be recreated. 500 | } 501 | 502 | @end 503 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/ext/NSBundle+CLExt.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+CLExt.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSBundle (CLExt) 12 | 13 | FOUNDATION_EXTERN NSString *CLString(NSString *key); 14 | 15 | + (instancetype)clLocalizedBundle; 16 | 17 | + (NSString *)clLocalizedStringForKey:(NSString *)key; 18 | 19 | + (NSString *)clLocalizedStringForKey:(NSString *)key value:(NSString *)value; 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/ext/NSBundle+CLExt.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+CLExt.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "NSBundle+CLExt.h" 10 | #import "CLPhotoManager.h" 11 | 12 | @implementation NSBundle (CLExt) 13 | 14 | NSString *CLString(NSString *key) { 15 | return [NSBundle clLocalizedStringForKey:key]; 16 | }; 17 | 18 | + (instancetype)clLocalizedBundle { 19 | static NSBundle *localBundle = nil; 20 | if (localBundle == nil) { 21 | localBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[CLPhotoManager class]] pathForResource:@"CLPhotoLib" ofType:@"bundle"]]; 22 | } 23 | return localBundle; 24 | } 25 | 26 | + (NSString *)clLocalizedStringForKey:(NSString *)key { 27 | return [self clLocalizedStringForKey:key value:nil]; 28 | } 29 | 30 | + (NSString *)clLocalizedStringForKey:(NSString *)key value:(NSString *)value { 31 | static NSBundle *bundle = nil; 32 | if (bundle == nil) { 33 | NSString *language = [NSLocale preferredLanguages].firstObject; 34 | if ([language hasPrefix:@"en"]) { 35 | language = @"en"; 36 | } else if ([language hasPrefix:@"zh"]) { 37 | if ([language rangeOfString:@"Hans"].location != NSNotFound) { 38 | language = @"zh-Hans"; // 简体中文 39 | } else { // zh-Hant\zh-HK\zh-TW 40 | language = @"zh-Hant"; // 繁體中文 41 | } 42 | } else { 43 | language = @"en"; 44 | } 45 | // 从*.bundle中查找资源 46 | bundle = [NSBundle bundleWithPath:[[NSBundle clLocalizedBundle] pathForResource:language ofType:@"lproj"]]; 47 | } 48 | value = [bundle localizedStringForKey:key value:value table:nil]; 49 | return [[NSBundle mainBundle] localizedStringForKey:key value:value table:nil]; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/ext/UIButton+CLExt.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+CLExt.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/2. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIButton (CLExt) 12 | 13 | - (void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/ext/UIButton+CLExt.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+CLExt.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/2. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "UIButton+CLExt.h" 10 | #import 11 | 12 | @implementation UIButton (CLExt) 13 | 14 | static char topNameKey; 15 | static char rightNameKey; 16 | static char bottomNameKey; 17 | static char leftNameKey; 18 | 19 | - (void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left { 20 | objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC); 21 | objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC); 22 | objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC); 23 | objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC); 24 | } 25 | 26 | - (CGRect)enlargedRect { 27 | NSNumber *topEdge = objc_getAssociatedObject(self, &topNameKey); 28 | NSNumber *rightEdge = objc_getAssociatedObject(self, &rightNameKey); 29 | NSNumber *bottomEdge = objc_getAssociatedObject(self, &bottomNameKey); 30 | NSNumber *leftEdge = objc_getAssociatedObject(self, &leftNameKey); 31 | if (topEdge && rightEdge && bottomEdge && leftEdge) { 32 | return CGRectMake(self.bounds.origin.x - leftEdge.floatValue, 33 | self.bounds.origin.y - topEdge.floatValue, 34 | self.bounds.size.width + leftEdge.floatValue + rightEdge.floatValue, 35 | self.bounds.size.height + topEdge.floatValue + bottomEdge.floatValue); 36 | } else { 37 | return self.bounds; 38 | } 39 | } 40 | 41 | - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { 42 | CGRect rect = [self enlargedRect]; 43 | if (CGRectEqualToRect(rect, self.bounds)) { 44 | return [super hitTest:point withEvent:event]; 45 | } 46 | return CGRectContainsPoint(rect, point) ? self : nil; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/ext/UIImage+CLExt.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+CLExt.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (CLExt) 12 | 13 | + (UIImage *)imageNamedFromBundle:(NSString *)name; 14 | 15 | /** 16 | * 颜色转图片 17 | * 18 | * @param color 颜色 19 | * 20 | * @return 图片 21 | */ 22 | + (UIImage *)imageWithColor:(UIColor *)color; 23 | 24 | + (UIImage *)imageWithColor:(UIColor *)color rectSize:(CGRect)imageSize; 25 | 26 | // 图片根据尺寸缩放 27 | + (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size; 28 | 29 | // 图片方向处理 30 | + (UIImage *)fixOrientation:(UIImage *)aImage isRotate:(BOOL)isRotate; 31 | 32 | /** 33 | * 返回一个Size 34 | * 35 | * @param imageSize 原图size 36 | * @param size 图片小的一边 不得超过的约束尺寸 37 | * 38 | * @return 返回size 39 | */ 40 | + (CGSize)getSizeWithImageSize:(CGSize)imageSize toSize:(CGSize)size; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/ext/UIImage+CLExt.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+CLExt.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "UIImage+CLExt.h" 10 | #import "NSBundle+CLExt.h" 11 | 12 | @implementation UIImage (CLExt) 13 | 14 | + (UIImage *)imageNamedFromBundle:(NSString *)name { 15 | UIImage *image = [UIImage imageNamed:[@"CLPhotoLib.bundle" stringByAppendingPathComponent:name]]; 16 | if (image) { 17 | return image; 18 | } else { 19 | image = [UIImage imageNamed:[@"Frameworks/CLPhotoLib.framework/CLPhotoLib.bundle" stringByAppendingPathComponent:name]]; 20 | return image; 21 | } 22 | return image; 23 | } 24 | 25 | + (UIImage *)imageWithColor:(UIColor *)color { 26 | CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); 27 | UIGraphicsBeginImageContext(rect.size); 28 | CGContextRef context = UIGraphicsGetCurrentContext(); 29 | 30 | CGContextSetFillColorWithColor(context, [color CGColor]); 31 | CGContextFillRect(context, rect); 32 | 33 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 34 | UIGraphicsEndImageContext(); 35 | return image; 36 | } 37 | 38 | + (UIImage *)imageWithColor:(UIColor *)color rectSize:(CGRect)imageSize { 39 | CGRect colorRect = imageSize; 40 | UIGraphicsBeginImageContextWithOptions(colorRect.size, NO, 0); 41 | [color setFill]; 42 | UIRectFill(colorRect); // Fill it with your color 43 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 44 | UIGraphicsEndImageContext(); 45 | return image; 46 | } 47 | 48 | + (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size { 49 | CGSize newSize = [self getSizeWithImageSize:image.size toSize:size]; 50 | UIGraphicsBeginImageContext(newSize); 51 | [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; 52 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 53 | UIGraphicsEndImageContext(); 54 | return newImage; 55 | } 56 | 57 | + (UIImage *)fixOrientation:(UIImage *)aImage isRotate:(BOOL)isRotate { 58 | if (!isRotate) return aImage; 59 | 60 | // No-op if the orientation is already correct 61 | if (aImage.imageOrientation == UIImageOrientationUp) 62 | return aImage; 63 | 64 | // We need to calculate the proper transformation to make the image upright. 65 | // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored. 66 | CGAffineTransform transform = CGAffineTransformIdentity; 67 | 68 | switch (aImage.imageOrientation) { 69 | case UIImageOrientationDown: 70 | case UIImageOrientationDownMirrored: 71 | transform = CGAffineTransformTranslate(transform, aImage.size.width, aImage.size.height); 72 | transform = CGAffineTransformRotate(transform, M_PI); 73 | break; 74 | 75 | case UIImageOrientationLeft: 76 | case UIImageOrientationLeftMirrored: 77 | transform = CGAffineTransformTranslate(transform, aImage.size.width, 0); 78 | transform = CGAffineTransformRotate(transform, M_PI_2); 79 | break; 80 | 81 | case UIImageOrientationRight: 82 | case UIImageOrientationRightMirrored: 83 | transform = CGAffineTransformTranslate(transform, 0, aImage.size.height); 84 | transform = CGAffineTransformRotate(transform, -M_PI_2); 85 | break; 86 | default: 87 | break; 88 | } 89 | 90 | switch (aImage.imageOrientation) { 91 | case UIImageOrientationUpMirrored: 92 | case UIImageOrientationDownMirrored: 93 | transform = CGAffineTransformTranslate(transform, aImage.size.width, 0); 94 | transform = CGAffineTransformScale(transform, -1, 1); 95 | break; 96 | 97 | case UIImageOrientationLeftMirrored: 98 | case UIImageOrientationRightMirrored: 99 | transform = CGAffineTransformTranslate(transform, aImage.size.height, 0); 100 | transform = CGAffineTransformScale(transform, -1, 1); 101 | break; 102 | default: 103 | break; 104 | } 105 | 106 | // Now we draw the underlying CGImage into a new context, applying the transform 107 | // calculated above. 108 | CGContextRef ctx = CGBitmapContextCreate(NULL, aImage.size.width, aImage.size.height, 109 | CGImageGetBitsPerComponent(aImage.CGImage), 0, 110 | CGImageGetColorSpace(aImage.CGImage), 111 | CGImageGetBitmapInfo(aImage.CGImage)); 112 | CGContextConcatCTM(ctx, transform); 113 | switch (aImage.imageOrientation) { 114 | case UIImageOrientationLeft: 115 | case UIImageOrientationLeftMirrored: 116 | case UIImageOrientationRight: 117 | case UIImageOrientationRightMirrored: 118 | // Grr... 119 | CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.height,aImage.size.width), aImage.CGImage); 120 | break; 121 | 122 | default: 123 | CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.width,aImage.size.height), aImage.CGImage); 124 | break; 125 | } 126 | 127 | // And now we just create a new UIImage from the drawing context 128 | CGImageRef cgimg = CGBitmapContextCreateImage(ctx); 129 | UIImage *img = [UIImage imageWithCGImage:cgimg]; 130 | CGContextRelease(ctx); 131 | CGImageRelease(cgimg); 132 | return img; 133 | } 134 | 135 | + (CGSize)getSizeWithImageSize:(CGSize)imageSize toSize:(CGSize)size { 136 | CGSize newSize; 137 | if (imageSize.width < imageSize.height) { 138 | if (imageSize.width < size.width) { 139 | return imageSize; 140 | } else { 141 | newSize = CGSizeMake(size.width, (int)size.width * imageSize.height / imageSize.width); 142 | } 143 | } else { 144 | if (imageSize.height < size.height) { 145 | return imageSize; 146 | } else { 147 | newSize = CGSizeMake(size.height * imageSize.width/imageSize.height, size.height); 148 | } 149 | } 150 | return newSize; 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/ext/UIView+CLExt.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+CLExt.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef enum : NSUInteger { 12 | CLOscillatoryAnimationToBigger, 13 | CLOscillatoryAnimationToSmaller, 14 | } CLOscillatoryAnimationType; 15 | 16 | @interface UIView (CLExt) 17 | 18 | @property CGPoint origin; 19 | @property CGSize size; 20 | 21 | @property (readonly) CGPoint bottomLeft; 22 | @property (readonly) CGPoint bottomRight; 23 | @property (readonly) CGPoint topRight; 24 | 25 | @property CGFloat height; 26 | @property CGFloat width; 27 | 28 | @property CGFloat top; 29 | @property CGFloat left; 30 | 31 | @property CGFloat bottom; 32 | @property CGFloat right; 33 | 34 | + (void)showOscillatoryAnimationWithLayer:(CALayer *)layer type:(CLOscillatoryAnimationType)type; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/ext/UIView+CLExt.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+CLExt.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "UIView+CLExt.h" 10 | 11 | @implementation UIView (CLExt) 12 | 13 | // Retrieve and set the origin 14 | - (CGPoint)origin { 15 | return self.frame.origin; 16 | } 17 | 18 | - (void)setOrigin:(CGPoint)aPoint { 19 | CGRect newframe = self.frame; 20 | newframe.origin = aPoint; 21 | self.frame = newframe; 22 | } 23 | 24 | // Retrieve and set the size 25 | - (CGSize)size { 26 | return self.frame.size; 27 | } 28 | 29 | - (void)setSize:(CGSize)aSize { 30 | CGRect newframe = self.frame; 31 | newframe.size = aSize; 32 | self.frame = newframe; 33 | } 34 | 35 | // Query other frame locations 36 | - (CGPoint)bottomRight { 37 | CGFloat x = self.frame.origin.x + self.frame.size.width; 38 | CGFloat y = self.frame.origin.y + self.frame.size.height; 39 | return CGPointMake(x, y); 40 | } 41 | 42 | - (CGPoint)bottomLeft { 43 | CGFloat x = self.frame.origin.x; 44 | CGFloat y = self.frame.origin.y + self.frame.size.height; 45 | return CGPointMake(x, y); 46 | } 47 | 48 | - (CGPoint)topRight { 49 | CGFloat x = self.frame.origin.x + self.frame.size.width; 50 | CGFloat y = self.frame.origin.y; 51 | return CGPointMake(x, y); 52 | } 53 | 54 | // Retrieve and set height, width, top, bottom, left, right 55 | - (CGFloat)height { 56 | return self.frame.size.height; 57 | } 58 | 59 | - (void)setHeight:(CGFloat)newheight { 60 | CGRect newframe = self.frame; 61 | newframe.size.height = newheight; 62 | self.frame = newframe; 63 | } 64 | 65 | - (CGFloat)width { 66 | return self.frame.size.width; 67 | } 68 | 69 | - (void)setWidth:(CGFloat)newwidth { 70 | CGRect newframe = self.frame; 71 | newframe.size.width = newwidth; 72 | self.frame = newframe; 73 | } 74 | 75 | - (CGFloat)top { 76 | return self.frame.origin.y; 77 | } 78 | 79 | - (void)setTop:(CGFloat)newtop { 80 | CGRect newframe = self.frame; 81 | newframe.origin.y = newtop; 82 | self.frame = newframe; 83 | } 84 | 85 | - (CGFloat)left { 86 | return self.frame.origin.x; 87 | } 88 | 89 | - (void)setLeft:(CGFloat)newleft { 90 | CGRect newframe = self.frame; 91 | newframe.origin.x = newleft; 92 | self.frame = newframe; 93 | } 94 | 95 | - (CGFloat)bottom { 96 | return self.frame.origin.y + self.frame.size.height; 97 | } 98 | 99 | - (void)setBottom:(CGFloat)newbottom { 100 | CGRect newframe = self.frame; 101 | newframe.origin.y = newbottom - self.frame.size.height; 102 | self.frame = newframe; 103 | } 104 | 105 | - (CGFloat)right { 106 | return self.frame.origin.x + self.frame.size.width; 107 | } 108 | 109 | - (void)setRight:(CGFloat)newright { 110 | CGFloat delta = newright - (self.frame.origin.x + self.frame.size.width); 111 | CGRect newframe = self.frame; 112 | newframe.origin.x += delta ; 113 | self.frame = newframe; 114 | } 115 | 116 | + (void)showOscillatoryAnimationWithLayer:(CALayer *)layer type:(CLOscillatoryAnimationType)type { 117 | NSNumber *animationScale1 = type == CLOscillatoryAnimationToBigger ? @(1.15) : @(0.5); 118 | NSNumber *animationScale2 = type == CLOscillatoryAnimationToBigger ? @(0.92) : @(1.15); 119 | 120 | [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ 121 | [layer setValue:animationScale1 forKeyPath:@"transform.scale"]; 122 | } completion:^(BOOL finished) { 123 | [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ 124 | [layer setValue:animationScale2 forKeyPath:@"transform.scale"]; 125 | } completion:^(BOOL finished) { 126 | [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ 127 | [layer setValue:@(1.0) forKeyPath:@"transform.scale"]; 128 | } completion:nil]; 129 | }]; 130 | }]; 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLAlbumTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLAlbumTableView.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/2. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CLAlbumModel; 12 | @interface CLAlbumTableView : UIView 13 | 14 | @property (nonatomic, strong) UIColor *tableColor; 15 | 16 | @property (nonatomic, copy) void(^didSelectAlbumBlock)(CLAlbumModel *model); 17 | 18 | @property (nonatomic, copy) void(^disMissAlbumBlock)(void); 19 | 20 | @property (nonatomic, copy) NSArray *albumArray; 21 | 22 | - (void)reloadData; 23 | - (void)showAlbumAnimated:(BOOL)animated; 24 | - (void)dismiss; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLAlbumTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLAlbumTableView.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/2. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLAlbumTableView.h" 10 | #import "CLPhotoCollectionCell.h" 11 | #import "CLPhotoModel.h" 12 | #import "CLConfig.h" 13 | #import "CLPhotoManager.h" 14 | #import "CLExtHeader.h" 15 | 16 | static NSString *cellIdentifier = @"CLAlbumTableViewCellIdentifier"; 17 | @interface CLAlbumTableView () { 18 | BOOL _isAnimated; 19 | } 20 | 21 | @property (nonatomic, strong) UITableView *tableView; 22 | @property (nonatomic, assign) CGFloat tableHeight; 23 | 24 | @end 25 | 26 | @implementation CLAlbumTableView 27 | 28 | - (instancetype)init { 29 | self = [super init]; 30 | if (self) { 31 | self.tableView.hidden = NO; 32 | } 33 | return self; 34 | } 35 | 36 | - (instancetype)initWithFrame:(CGRect)frame { 37 | self = [super initWithFrame:frame]; 38 | if (self) { 39 | self.tableView.hidden = NO; 40 | } 41 | return self; 42 | } 43 | 44 | - (UITableView *)tableView { 45 | if (!_tableView) { 46 | _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 47 | _tableView.delegate = self; 48 | _tableView.dataSource = self; 49 | _tableView.estimatedSectionHeaderHeight = 0; 50 | _tableView.estimatedSectionFooterHeight = 0; 51 | _tableView.tableFooterView = [UIView new]; 52 | if (@available(iOS 11.0, *)) { 53 | [_tableView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentAlways]; 54 | } 55 | [self addSubview:_tableView]; 56 | } 57 | return _tableView; 58 | } 59 | 60 | #pragma mark - 61 | #pragma mark -- UITableViewDataSource & UITableViewDelegate -- 62 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 63 | return _albumArray.count; 64 | } 65 | 66 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 67 | CLAlbumTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 68 | if (!cell) { 69 | cell = [[CLAlbumTableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; 70 | } 71 | if (_albumArray.count > indexPath.row) { 72 | cell.model = _albumArray[indexPath.row]; 73 | } 74 | return cell; 75 | } 76 | 77 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 78 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 79 | if (_albumArray.count > indexPath.row) { 80 | if (self.didSelectAlbumBlock) { 81 | self.didSelectAlbumBlock(_albumArray[indexPath.row]); 82 | } 83 | } 84 | [self dismiss]; 85 | } 86 | 87 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 88 | return CLAlbumRowHeight(); 89 | } 90 | 91 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 92 | // iso 7 93 | if ([cell respondsToSelector:@selector(setSeparatorInset:)]) { 94 | [cell setSeparatorInset:UIEdgeInsetsZero]; 95 | } 96 | // ios 8 97 | if ([cell respondsToSelector:@selector(setLayoutMargins:)]) { 98 | [cell setLayoutMargins:UIEdgeInsetsZero]; 99 | } 100 | } 101 | 102 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 103 | [super touchesBegan:touches withEvent:event]; 104 | [self dismiss]; 105 | } 106 | 107 | #pragma mark - 108 | #pragma mark -- -- -- -- -- - Public Methond - -- -- -- -- -- 109 | - (void)setAlbumArray:(NSArray *)albumArray { 110 | _albumArray = albumArray; 111 | [self reloadData]; 112 | } 113 | 114 | - (void)reloadData { 115 | [self.tableView reloadData]; 116 | } 117 | 118 | - (void)showAlbumAnimated:(BOOL)animated { 119 | _isAnimated = animated; 120 | if (_isAnimated) { 121 | if (CLAlbumRowHeight() * _albumArray.count > self.height*CLAlbumDropDownScale) { 122 | self.tableView.scrollEnabled = YES; 123 | _tableHeight = self.height*CLAlbumDropDownScale; 124 | } else { 125 | self.tableView.scrollEnabled = NO; 126 | _tableHeight = CLAlbumRowHeight() * self.albumArray.count; 127 | } 128 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; 129 | self.tableView.frame = CGRectMake(0, - _tableHeight, self.width, _tableHeight); 130 | self.hidden = NO; 131 | cl_weakSelf(self); 132 | [UIView animateWithDuration:CLAlbumDropDownAnimationTime animations:^{ 133 | weakSelf.tableView.frame = CGRectMake(0, 0, weakSelf.width, weakSelf.tableHeight); 134 | }]; 135 | } else { 136 | self.tableView.scrollEnabled = YES; 137 | } 138 | } 139 | 140 | - (void)layoutSubviews { 141 | [super layoutSubviews]; 142 | if (_isAnimated) { 143 | if (CLAlbumRowHeight() * _albumArray.count > self.height*CLAlbumDropDownScale) { 144 | _tableHeight = self.height*CLAlbumDropDownScale; 145 | } else { 146 | _tableHeight = CLAlbumRowHeight() * self.albumArray.count; 147 | } 148 | self.tableView.frame = CGRectMake(0, 0, self.width, _tableHeight); 149 | } else { 150 | self.tableView.frame = self.bounds; 151 | } 152 | } 153 | 154 | - (void)setTableColor:(UIColor *)tableColor { 155 | _tableColor = tableColor; 156 | self.tableView.backgroundColor = _tableColor; 157 | } 158 | 159 | - (void)dismiss { 160 | if (!_isAnimated) { 161 | return; 162 | } 163 | cl_weakSelf(self); 164 | [UIView animateWithDuration:CLAlbumPackUpAnimationTime animations:^{ 165 | weakSelf.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0]; 166 | weakSelf.tableView.frame = CGRectMake(0, -weakSelf.tableHeight, weakSelf.width, weakSelf.tableHeight); 167 | } completion:^(BOOL finished) { 168 | self.hidden = YES; 169 | if (self.disMissAlbumBlock) { 170 | self.disMissAlbumBlock(); 171 | } 172 | }]; 173 | } 174 | 175 | @end 176 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLEditImageController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLEditImageController.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/23. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CLPhotoModel; 12 | @interface CLEditImageController : UIViewController 13 | 14 | @property (nonatomic, strong) CLPhotoModel *model; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLEditImageController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLEditImageController.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/23. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLEditImageController.h" 10 | #import "CLPickerRootController.h" 11 | #import "CLExtHeader.h" 12 | #import "CLConfig.h" 13 | 14 | @interface CLEditImageController () 15 | 16 | @end 17 | 18 | @implementation CLEditImageController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:CLString(@"CLText_NotSupport") preferredStyle:UIAlertControllerStyleAlert]; 24 | UIAlertAction *action = [UIAlertAction actionWithTitle:CLString(@"CLText_OK") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 25 | [self.navigationController popViewControllerAnimated:YES]; 26 | }]; 27 | [alert addAction:action]; 28 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { 29 | UIPopoverPresentationController *popPresenter = [alert popoverPresentationController]; 30 | popPresenter.sourceView = self.view; 31 | popPresenter.sourceRect = self.view.bounds; 32 | [self presentViewController:alert animated:YES completion:nil]; 33 | } else { 34 | [self presentViewController:alert animated:YES completion:nil]; 35 | } 36 | } 37 | 38 | #pragma mark - 39 | #pragma mark -- Lazy Loads -- 40 | - (CLPickerRootController *)picker { 41 | return (CLPickerRootController *)self.navigationController; 42 | } 43 | 44 | - (void)didReceiveMemoryWarning { 45 | [super didReceiveMemoryWarning]; 46 | // Dispose of any resources that can be recreated. 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLEditVideoController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLEditVideoController.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/15. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CLPhotoModel; 12 | @interface CLEditVideoController : UIViewController 13 | 14 | @property (nonatomic, strong) CLPhotoModel *model; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLExtHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLExtHeader.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/23. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #ifndef CLExtHeader_h 10 | #define CLExtHeader_h 11 | 12 | #import "NSBundle+CLExt.h" 13 | #import "UIView+CLExt.h" 14 | #import "UIImage+CLExt.h" 15 | #import "UIButton+CLExt.h" 16 | 17 | #import "CLPhotoModel.h" 18 | #import "CLPhotoManager.h" 19 | #import "CLEditManager.h" 20 | 21 | #endif /* CLExtHeader_h */ 22 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPhotoCollectionCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoCollectionCell.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/2. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CLPhotoModel; 12 | @interface CLPhotoCollectionCell : UICollectionViewCell 13 | 14 | @property (nonatomic, strong) UIButton *selectButton; 15 | 16 | @property (nonatomic, strong) CLPhotoModel *model; 17 | 18 | @property (nonatomic, copy) void(^didSelectPhotoBlock)(BOOL isSelected); 19 | 20 | @property (nonatomic, assign) BOOL selectBtnSelect; 21 | 22 | @property (nonatomic, assign) BOOL allowImgMultiple; 23 | 24 | @end 25 | 26 | @interface CLTakePhotoCell : UICollectionViewCell 27 | 28 | @property (nonatomic) BOOL allowSelectVideo; 29 | @property (nonatomic) BOOL showCaptureOnCell; 30 | 31 | @end 32 | 33 | @class CLAlbumModel; 34 | @interface CLAlbumTableViewCell : UITableViewCell 35 | 36 | @property (nonatomic, strong) CLAlbumModel *model; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPhotoCollectionCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoCollectionCell.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/2. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLPhotoCollectionCell.h" 10 | #import "CLConfig.h" 11 | #import "CLExtHeader.h" 12 | 13 | #pragma mark - 14 | #pragma mark -- CLPhotoCollectionCell -- 15 | static CGFloat selectBtnWidth = 35.0f; 16 | static CGFloat bottomViewHeight = 16.0f; 17 | static CGFloat CLTimeLabelFontSize = 12.0f; 18 | 19 | @interface CLPhotoCollectionCell () 20 | 21 | @property (nonatomic, strong) UIImageView *imageView; 22 | @property (nonatomic, strong) UIView *shadowView; 23 | @property (nonatomic, strong) UIImageView *bottomView; 24 | 25 | @property (nonatomic, strong) UIImageView *liveImageView; 26 | @property (nonatomic, strong) UIImageView *videoImageView; 27 | @property (nonatomic, strong) UILabel *typeLabel; 28 | 29 | @property (nonatomic, copy) NSString *localIdentifier; 30 | @property (nonatomic, assign) PHImageRequestID imageRequestID; 31 | 32 | @end 33 | 34 | @implementation CLPhotoCollectionCell 35 | 36 | - (void)setModel:(CLPhotoModel *)model { 37 | _model = model; 38 | _selectButton.hidden = NO; 39 | _selectButton.enabled = YES; 40 | if (_model.type == CLAssetMediaTypeVideo) { 41 | _selectButton.hidden = YES; 42 | _selectButton.enabled = NO; 43 | _liveImageView.hidden = YES; 44 | self.bottomView.hidden = NO; 45 | self.videoImageView.hidden = NO; 46 | self.typeLabel.text = model.timeFormat; 47 | } else if (_model.type == CLAssetMediaTypeGif) { 48 | _videoImageView.hidden = YES; 49 | _liveImageView.hidden = YES; 50 | self.bottomView.hidden = !CLAllowSelectGif; 51 | self.typeLabel.text = @"GIF"; 52 | } else if (_model.type == CLAssetMediaTypeLivePhoto) { 53 | _videoImageView.hidden = YES; 54 | self.bottomView.hidden = !CLAllowSelectLivePhoto; 55 | self.liveImageView.hidden = NO; 56 | self.typeLabel.text = @"Live"; 57 | } else { 58 | self.bottomView.hidden = YES; 59 | } 60 | if (!_allowImgMultiple || _model.type == CLAssetMediaTypeVideo) { 61 | _selectButton.hidden = YES; 62 | _selectButton.enabled = NO; 63 | } 64 | _selectButton.selected = model.isSelected; 65 | _shadowView.alpha = model.isSelected; 66 | if (model.asset && self.imageRequestID >= PHInvalidImageRequestID) { 67 | [[PHCachingImageManager defaultManager] cancelImageRequest:self.imageRequestID]; 68 | } 69 | self.localIdentifier = model.asset.localIdentifier; 70 | self.imageView.image = nil; 71 | cl_weakSelf(self); 72 | self.imageRequestID = [CLPhotoShareManager requestCustomImageForAsset:model.asset size:CGSizeMake(self.width * [UIScreen mainScreen].scale, self.width * [UIScreen mainScreen].scale) completion:^(UIImage *image, NSDictionary *info) { 73 | cl_strongSelf(weakSelf); 74 | if (!strongSelf) { 75 | return; 76 | } 77 | if ([strongSelf.localIdentifier isEqualToString:model.asset.localIdentifier]) { 78 | strongSelf.imageView.image = image; 79 | } 80 | if (![[info objectForKey:PHImageResultIsDegradedKey] boolValue]) { 81 | strongSelf.imageRequestID = -1; 82 | } 83 | }]; 84 | } 85 | 86 | #pragma mark -- Initial Methods -- 87 | - (instancetype)initWithFrame:(CGRect)frame { 88 | self = [super initWithFrame:frame]; 89 | if (self) { 90 | self.imageView.hidden = NO; 91 | self.shadowView.alpha = 0; 92 | self.selectButton.hidden = YES; 93 | self.bottomView.hidden = YES; 94 | self.liveImageView.hidden = NO; 95 | self.videoImageView.hidden = NO; 96 | self.typeLabel.hidden = NO; 97 | } 98 | return self; 99 | } 100 | 101 | - (void)layoutSubviews { 102 | [super layoutSubviews]; 103 | _imageView.frame = self.bounds; 104 | _shadowView.frame = _imageView.bounds; 105 | _selectButton.frame = CGRectMake(self.width - selectBtnWidth, 0, selectBtnWidth, selectBtnWidth); 106 | _bottomView.frame = CGRectMake(0, self.height-bottomViewHeight, self.width, bottomViewHeight); 107 | _liveImageView.frame = CGRectMake(5, (bottomViewHeight - 16)/2.0 - 2, 16, 16); 108 | _videoImageView.frame = CGRectMake(5, (bottomViewHeight - 11)/2.0, 16, 11); 109 | _typeLabel.frame = CGRectMake(30, 0, self.width - 35, bottomViewHeight); 110 | } 111 | 112 | #pragma mark -- Lazy Loads -- 113 | - (UIImageView *)imageView { 114 | if (!_imageView) { 115 | _imageView = [[UIImageView alloc] init]; 116 | _imageView.contentMode = UIViewContentModeScaleAspectFill; 117 | _imageView.clipsToBounds = YES; 118 | [self.contentView addSubview:_imageView]; 119 | } 120 | return _imageView; 121 | } 122 | 123 | - (UIView *)shadowView { 124 | if (!_shadowView) { 125 | _shadowView = [UIView new]; 126 | _shadowView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:CLShadowViewAlpha]; 127 | [self.imageView addSubview:_shadowView]; 128 | } 129 | return _shadowView; 130 | } 131 | 132 | - (UIButton *)selectButton { 133 | if (!_selectButton) { 134 | _selectButton = [[UIButton alloc] init]; 135 | [_selectButton setImage:[UIImage imageNamedFromBundle:@"btn_photo_unselected"] forState:UIControlStateNormal]; 136 | [_selectButton setImage:[UIImage imageNamedFromBundle:@"btn_photo_unselected"] forState:UIControlStateHighlighted]; 137 | [_selectButton setImage:[UIImage imageNamedFromBundle:@"btn_photo_selected"] forState:UIControlStateSelected]; 138 | [_selectButton setImage:[UIImage imageNamedFromBundle:@"btn_photo_selected"] forState:UIControlStateSelected|UIControlStateHighlighted]; 139 | [_selectButton addTarget:self action:@selector(clickSelectButton:) forControlEvents:UIControlEventTouchUpInside]; 140 | [self.contentView addSubview:_selectButton]; 141 | [_selectButton setEnlargeEdgeWithTop:0 right:0 bottom:10 left:10]; 142 | } 143 | return _selectButton; 144 | } 145 | 146 | - (UIImageView *)bottomView { 147 | if (!_bottomView) { 148 | _bottomView = [[UIImageView alloc] initWithImage:[UIImage imageNamedFromBundle:@"clicon_photo_shadow"]]; 149 | [self.contentView addSubview:_bottomView]; 150 | } 151 | return _bottomView; 152 | } 153 | 154 | - (UIImageView *)liveImageView { 155 | if (!_liveImageView) { 156 | _liveImageView = [[UIImageView alloc] init]; 157 | _liveImageView.image = [UIImage imageNamedFromBundle:@"clicon_photo_live"]; 158 | _liveImageView.contentMode = UIViewContentModeScaleAspectFit; 159 | [self.bottomView addSubview:_liveImageView]; 160 | } 161 | return _liveImageView; 162 | } 163 | 164 | - (UIImageView *)videoImageView { 165 | if (!_videoImageView) { 166 | _videoImageView = [[UIImageView alloc] init]; 167 | _videoImageView.image = [UIImage imageNamedFromBundle:@"clicon_photo_video"]; 168 | [self.bottomView addSubview:_videoImageView]; 169 | } 170 | return _videoImageView; 171 | } 172 | 173 | - (UILabel *)typeLabel { 174 | if (!_typeLabel) { 175 | _typeLabel = [[UILabel alloc] init]; 176 | _typeLabel.textAlignment = NSTextAlignmentRight; 177 | _typeLabel.font = [UIFont systemFontOfSize:CLTimeLabelFontSize]; 178 | _typeLabel.textColor = [UIColor whiteColor]; 179 | _typeLabel.adjustsFontSizeToFitWidth = YES; 180 | [self.bottomView addSubview:_typeLabel]; 181 | } 182 | return _typeLabel; 183 | } 184 | 185 | #pragma mark -- Target Methods -- 186 | - (void)clickSelectButton:(UIButton *)sender { 187 | if (self.didSelectPhotoBlock) { 188 | self.didSelectPhotoBlock(sender.selected); 189 | } 190 | } 191 | 192 | - (void)setSelectBtnSelect:(BOOL)selectBtnSelect { 193 | _selectBtnSelect = selectBtnSelect; 194 | self.selectButton.selected = _selectBtnSelect; 195 | if (self.selectButton.isSelected) { 196 | cl_weakSelf(self); 197 | [UIView animateWithDuration:CLLittleControlAnimationTime animations:^{ 198 | weakSelf.shadowView.alpha = 1; 199 | }]; 200 | [UIView showOscillatoryAnimationWithLayer:self.selectButton.layer type:CLOscillatoryAnimationToBigger]; 201 | } else { 202 | _shadowView.alpha = 0; 203 | } 204 | } 205 | 206 | @end 207 | 208 | #pragma mark - 209 | #pragma mark -- CLTakePhotoCell -- 210 | @import AVFoundation; 211 | @interface CLTakePhotoCell () 212 | 213 | @property (nonatomic, strong) UIImageView *imageView; 214 | 215 | @property (nonatomic, strong) AVCaptureSession *session; 216 | @property (nonatomic, strong) AVCaptureDeviceInput *videoInput; 217 | @property (nonatomic, strong) AVCaptureStillImageOutput *stillImageOutPut; 218 | @property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; 219 | 220 | @end 221 | @implementation CLTakePhotoCell 222 | 223 | - (void)setShowCaptureOnCell:(BOOL)showCaptureOnCell { 224 | _showCaptureOnCell = showCaptureOnCell; 225 | if (_showCaptureOnCell) { 226 | [self startCapture]; 227 | self.imageView.image = [UIImage imageNamedFromBundle:@"clicon_take_camera"]; 228 | self.imageView.contentMode = UIViewContentModeScaleAspectFit; 229 | } else { 230 | [self removeSession]; 231 | if (_allowSelectVideo) { 232 | self.imageView.image = [UIImage imageNamedFromBundle:@"clicon_take_video"]; 233 | } else { 234 | self.imageView.image = [UIImage imageNamedFromBundle:@"clicon_take_photo"]; 235 | } 236 | self.imageView.contentMode = UIViewContentModeScaleAspectFill; 237 | } 238 | } 239 | 240 | - (instancetype)initWithFrame:(CGRect)frame { 241 | self = [super initWithFrame:frame]; 242 | if (self) { 243 | 244 | } 245 | return self; 246 | } 247 | 248 | - (void)layoutSubviews { 249 | [super layoutSubviews]; 250 | if (_showCaptureOnCell) { 251 | _imageView.frame = CGRectMake(0, 0, self.width/3.0, self.width/3.0); 252 | _imageView.center = self.center; 253 | } else { 254 | _imageView.frame = self.bounds; 255 | } 256 | _previewLayer.frame = self.contentView.layer.bounds; 257 | } 258 | 259 | - (UIImageView *)imageView { 260 | if (!_imageView) { 261 | _imageView = [[UIImageView alloc] init]; 262 | _imageView.clipsToBounds = YES; 263 | [self addSubview:_imageView]; 264 | } 265 | return _imageView; 266 | } 267 | 268 | - (void)restartCapture { 269 | if (_session) { 270 | [_session stopRunning]; 271 | } 272 | [self startCapture]; 273 | } 274 | 275 | - (void)startCapture { 276 | AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; 277 | if (![UIImagePickerController isSourceTypeAvailable: 278 | UIImagePickerControllerSourceTypeCamera] || 279 | status == AVAuthorizationStatusRestricted || 280 | status == AVAuthorizationStatusDenied) { 281 | return; 282 | } 283 | 284 | if (self.session && [self.session isRunning]) { 285 | return; 286 | } 287 | [self.session stopRunning]; 288 | [self.session removeInput:self.videoInput]; 289 | [self.session removeOutput:self.stillImageOutPut]; 290 | self.session = nil; 291 | [self.previewLayer removeFromSuperlayer]; 292 | self.previewLayer = nil; 293 | 294 | self.session = [[AVCaptureSession alloc] init]; 295 | self.videoInput = [AVCaptureDeviceInput deviceInputWithDevice:[self backCamera] error:nil]; 296 | self.stillImageOutPut = [[AVCaptureStillImageOutput alloc] init]; 297 | //这是输出流的设置参数AVVideoCodecJPEG参数表示以JPEG的图片格式输出图片 298 | NSDictionary *dicOutputSetting = [NSDictionary dictionaryWithObject:AVVideoCodecJPEG forKey:AVVideoCodecKey]; 299 | [self.stillImageOutPut setOutputSettings:dicOutputSetting]; 300 | 301 | if ([self.session canAddInput:self.videoInput]) { 302 | [self.session addInput:self.videoInput]; 303 | } 304 | if ([self.session canAddOutput:self.stillImageOutPut]) { 305 | [self.session addOutput:self.stillImageOutPut]; 306 | } 307 | 308 | self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session]; 309 | [self.contentView.layer setMasksToBounds:YES]; 310 | 311 | [self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; 312 | [self.contentView.layer insertSublayer:self.previewLayer atIndex:0]; 313 | 314 | [self.session startRunning]; 315 | } 316 | 317 | - (AVCaptureDevice *)backCamera { 318 | return [self cameraWithPosition:AVCaptureDevicePositionBack]; 319 | } 320 | 321 | - (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position { 322 | NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; 323 | for (AVCaptureDevice *device in devices) { 324 | if ([device position] == position) { 325 | return device; 326 | } 327 | } 328 | return nil; 329 | } 330 | 331 | - (void)removeSession { 332 | if (_session) { 333 | [_session stopRunning]; 334 | _session = nil; 335 | } 336 | } 337 | 338 | - (void)dealloc { 339 | CLLog(@"%s", __func__); 340 | [self removeSession]; 341 | } 342 | 343 | @end 344 | 345 | #pragma mark - 346 | #pragma mark -- CLAlbumTableViewCell -- 347 | 348 | static CGFloat rightSelectButtonWidth = 18.0f; 349 | static CGFloat CLAlbumTitleFontSize = 15.0f; 350 | static CGFloat CLAlbumSelectFontSize = 14.0f; 351 | 352 | @interface CLAlbumTableViewCell () 353 | 354 | @property (nonatomic, strong) UIImageView *headImageView; 355 | @property (nonatomic, strong) UILabel *titleLabel; 356 | @property (nonatomic, strong) UIButton *selectButton; 357 | @property (nonatomic, strong) UIImageView *arrowImageView; 358 | 359 | @property (nonatomic, copy) NSString *localIdentifier; 360 | @end 361 | @implementation CLAlbumTableViewCell 362 | 363 | - (void)setModel:(CLAlbumModel *)model { 364 | _model = model; 365 | self.headImageView.hidden = NO; 366 | cl_weakSelf(self); 367 | self.localIdentifier = model.firstAsset.localIdentifier; 368 | [CLPhotoShareManager requestCustomImageForAsset:model.firstAsset size:CGSizeMake(CLAlbumRowHeight() * [UIScreen mainScreen].scale, CLAlbumRowHeight() * [UIScreen mainScreen].scale) completion:^(UIImage *image, NSDictionary *info) { 369 | cl_strongSelf(weakSelf); 370 | if ([strongSelf.localIdentifier isEqualToString:model.firstAsset.localIdentifier]) { 371 | strongSelf.headImageView.image = image?:[UIImage imageNamedFromBundle:@"icon_default_photo"]; 372 | } 373 | }]; 374 | if (_model.title) { 375 | NSMutableAttributedString *nameString = [[NSMutableAttributedString alloc] initWithString:_model.title attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:CLAlbumTitleFontSize],NSForegroundColorAttributeName:[UIColor blackColor]}]; 376 | NSAttributedString *countString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" (%ld)", (long)_model.count] attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:CLAlbumTitleFontSize],NSForegroundColorAttributeName:[UIColor lightGrayColor]}]; 377 | [nameString appendAttributedString:countString]; 378 | self.titleLabel.attributedText = nameString; 379 | } 380 | if (_model.selectedCount) { 381 | self.selectButton.hidden = NO; 382 | [self.selectButton setTitle:[NSString stringWithFormat:@"%lu", (unsigned long)_model.selectedCount] forState:UIControlStateNormal]; 383 | } else { 384 | _selectButton.hidden = YES; 385 | } 386 | } 387 | 388 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 389 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 390 | if (self) { 391 | self.accessoryView = self.arrowImageView; 392 | } 393 | return self; 394 | } 395 | 396 | -(void)layoutSubviews { 397 | [super layoutSubviews]; 398 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { 399 | CGFloat hleft = self.width - CGRectGetMinX(self.accessoryView.frame) - 15; 400 | _headImageView.frame = CGRectMake(hleft>5?hleft:10, 0.5, self.height, self.height - 1.5); 401 | } else { 402 | _headImageView.frame = CGRectMake(10, 0.5, self.height, self.height - 1.5); 403 | } 404 | _titleLabel.frame = CGRectMake(_headImageView.right + 10, 0, self.width - _headImageView.width - rightSelectButtonWidth - 60, self.height); 405 | _selectButton.frame = CGRectMake(CGRectGetMinX(self.accessoryView.frame) - rightSelectButtonWidth - 2, (self.height - rightSelectButtonWidth)/2.0, rightSelectButtonWidth, rightSelectButtonWidth); 406 | } 407 | 408 | #pragma mark -- Lazy Loads -- 409 | - (UIImageView *)headImageView { 410 | if (!_headImageView) { 411 | _headImageView = [[UIImageView alloc] init]; 412 | _headImageView.contentMode = UIViewContentModeScaleAspectFill; 413 | _headImageView.clipsToBounds = YES; 414 | [self.contentView addSubview:_headImageView]; 415 | } 416 | return _headImageView; 417 | } 418 | 419 | - (UILabel *)titleLabel { 420 | if (!_titleLabel) { 421 | _titleLabel = [[UILabel alloc] init]; 422 | _titleLabel.font = [UIFont boldSystemFontOfSize:CLAlbumTitleFontSize]; 423 | _titleLabel.textColor = [UIColor blackColor]; 424 | _titleLabel.textAlignment = NSTextAlignmentLeft; 425 | _titleLabel.adjustsFontSizeToFitWidth = YES; 426 | [self.contentView addSubview:_titleLabel]; 427 | } 428 | return _titleLabel; 429 | } 430 | 431 | - (UIButton *)selectButton { 432 | if (!_selectButton) { 433 | _selectButton = [[UIButton alloc] init]; 434 | _selectButton.layer.cornerRadius = rightSelectButtonWidth/2.0; 435 | _selectButton.clipsToBounds = YES; 436 | _selectButton.backgroundColor = CLAlbumSeletedRoundColor; 437 | _selectButton.titleLabel.font = [UIFont systemFontOfSize:CLAlbumSelectFontSize]; 438 | _selectButton.userInteractionEnabled = NO; 439 | _selectButton.titleLabel.adjustsFontSizeToFitWidth = YES; 440 | [_selectButton setTitleColor:CLAlbumSeletedNumberColor forState:UIControlStateNormal]; 441 | [self.contentView addSubview:_selectButton]; 442 | } 443 | return _selectButton; 444 | } 445 | 446 | - (UIImageView *)arrowImageView { 447 | if (!_arrowImageView) { 448 | _arrowImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 15, 15)]; 449 | [_arrowImageView setImage:[UIImage imageNamedFromBundle:@"clicon_photo_arrow"]]; 450 | } 451 | return _arrowImageView; 452 | } 453 | 454 | @end 455 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPhotoModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoModel.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/2. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSUInteger, CLAssetMediaType) { 13 | CLAssetMediaTypeUnknown, 14 | CLAssetMediaTypeImage, 15 | CLAssetMediaTypeGif, 16 | CLAssetMediaTypeLivePhoto, 17 | CLAssetMediaTypeVideo, 18 | CLAssetMediaTypeAudio, 19 | CLAssetMediaTypeNetImage, 20 | }; 21 | 22 | @interface CLPhotoModel : NSObject 23 | 24 | @property (nonatomic, strong) PHAsset *asset; // asset对象 25 | @property (nonatomic, assign) BOOL isSelected; // The select status of a photo, default is No 26 | @property (nonatomic, assign) CLAssetMediaType type; 27 | @property (nonatomic, copy) NSString *timeFormat; // video or audio duration 28 | @property (nonatomic, assign) CGFloat duration; // video or audio duration 29 | 30 | + (instancetype)modelWithAsset:(PHAsset *)asset; 31 | + (instancetype)modelWithAsset:(PHAsset *)asset type:(CLAssetMediaType)type; 32 | + (instancetype)modelWithAsset:(PHAsset *)asset type:(CLAssetMediaType)type duration:(CGFloat)duration; 33 | 34 | + (NSString *)timeWithFormat:(CGFloat)dur; 35 | 36 | + (CLAssetMediaType)transformAssetType:(PHAsset *)asset; 37 | 38 | @end 39 | 40 | @interface CLAlbumModel : NSObject 41 | 42 | @property (nonatomic, copy) NSString *title; 43 | @property (nonatomic, assign) NSInteger count; 44 | @property (nonatomic, strong) PHFetchResult *result; 45 | // 相册第一张图asset对象 46 | @property (nonatomic, strong) PHAsset *firstAsset; 47 | 48 | @property (nonatomic, copy) NSArray *models; 49 | @property (nonatomic, copy) NSArray *selectedModels; 50 | @property (nonatomic, assign) NSUInteger selectedCount; 51 | 52 | @property (nonatomic, assign) BOOL isCameraRoll; 53 | 54 | @end 55 | 56 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPhotoModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoModel.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/2. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLPhotoModel.h" 10 | #import "CLPhotoManager.h" 11 | 12 | @implementation CLPhotoModel 13 | 14 | + (instancetype)modelWithAsset:(PHAsset *)asset { 15 | CLPhotoModel *model = [[CLPhotoModel alloc] init]; 16 | model.asset = asset; 17 | model.isSelected = NO; 18 | model.type = [CLPhotoModel transformAssetType:asset]; 19 | return model; 20 | } 21 | 22 | + (instancetype)modelWithAsset:(PHAsset *)asset type:(CLAssetMediaType)type { 23 | CLPhotoModel *model = [[CLPhotoModel alloc] init]; 24 | model.asset = asset; 25 | model.isSelected = NO; 26 | model.type = type; 27 | return model; 28 | } 29 | 30 | + (instancetype)modelWithAsset:(PHAsset *)asset type:(CLAssetMediaType)type duration:(CGFloat)duration { 31 | CLPhotoModel *model = [self modelWithAsset:asset type:type]; 32 | model.duration = duration; 33 | model.timeFormat = [self timeWithFormat:duration]; 34 | return model; 35 | } 36 | 37 | + (NSString *)timeWithFormat:(CGFloat)dur { 38 | NSInteger duration = (NSInteger)round(dur); 39 | if (duration < 60) { 40 | return [NSString stringWithFormat:@"00:%02ld", (long)duration]; 41 | } else if (duration < 3600) { 42 | NSInteger m = duration / 60; 43 | NSInteger s = duration % 60; 44 | return [NSString stringWithFormat:@"%02ld:%02ld", (long)m, (long)s]; 45 | } else { 46 | NSInteger h = duration / 3600; 47 | NSInteger m = (duration % 3600) / 60; 48 | NSInteger s = duration % 60; 49 | return [NSString stringWithFormat:@"%02ld:%02ld:%02ld", (long)h, (long)m, (long)s]; 50 | } 51 | } 52 | 53 | + (CLAssetMediaType)transformAssetType:(PHAsset *)asset { 54 | switch (asset.mediaType) { 55 | case PHAssetMediaTypeAudio: 56 | return CLAssetMediaTypeAudio; 57 | case PHAssetMediaTypeVideo: 58 | return CLAssetMediaTypeVideo; 59 | case PHAssetMediaTypeImage: 60 | if ([[asset valueForKey:@"filename"] hasSuffix:@"GIF"]) return CLAssetMediaTypeGif; 61 | if (@available(iOS 9.1, *)) { 62 | if (asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive || asset.mediaSubtypes == 10) return CLAssetMediaTypeLivePhoto; 63 | } 64 | return CLAssetMediaTypeImage; 65 | default: 66 | return CLAssetMediaTypeUnknown; 67 | } 68 | } 69 | 70 | @end 71 | 72 | @implementation CLAlbumModel 73 | 74 | - (void)setSelectedModels:(NSArray *)selectedModels { 75 | _selectedModels = selectedModels; 76 | if (_models) { 77 | [self checkSelectedModels]; 78 | } 79 | } 80 | 81 | - (void)checkSelectedModels { 82 | self.selectedCount = [CLPhotoManager checkSelcectModelInArray:_models selArray:_selectedModels]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPhotosViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoViewController.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/1. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | FOUNDATION_EXTERN NSNotificationName const CLPhotoLibReloadAlbumList; 12 | 13 | @class CLAlbumModel; 14 | @interface CLPhotosViewController : UIViewController 15 | 16 | @property (nonatomic, strong) CLAlbumModel *albumModel; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPickerToolBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPickerToolBar.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/4. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CLDoneButton; 12 | @interface CLPickerToolBar : UIView 13 | 14 | @property (nonatomic, strong) UIButton *previewBtn; 15 | @property (nonatomic, strong) UIButton *editBtn; 16 | @property (nonatomic, strong) UIButton *originalBtn; 17 | @property (nonatomic, strong) UIButton *tipLabel; 18 | @property (nonatomic, strong) CLDoneButton *doneBtn; 19 | 20 | @property (nonatomic, strong) UIColor *titleColor; 21 | @property (nonatomic, assign) CGFloat fontSize; 22 | 23 | @property (nonatomic, copy) void(^clickPreviewBlock)(void); 24 | @property (nonatomic, copy) void(^clickEditBlock)(void); 25 | @property (nonatomic, copy) void(^clickOriginalBlock)(BOOL selected); 26 | 27 | @property (nonatomic, assign) BOOL editSelect; 28 | 29 | - (void)startAnimating; 30 | - (void)stopAnimating; 31 | 32 | @end 33 | 34 | @interface CLDoneButton : UIButton 35 | 36 | @property (nonatomic, strong) UIColor *numberColor; 37 | @property (nonatomic, strong) UIColor *titleColor; 38 | @property (nonatomic, assign) CGFloat titleFontSize; 39 | 40 | @property (nonatomic, assign) NSInteger number; 41 | 42 | @property (nonatomic, copy) void(^clickDoneBlock)(void); 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPickerToolBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLPickerToolBar.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/4. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLPickerToolBar.h" 10 | #import "CLConfig.h" 11 | #import "CLExtHeader.h" 12 | 13 | static CGFloat itemSpacing = 10.0f; 14 | static CGFloat itemMargin = 10.0f; 15 | 16 | @interface CLPickerToolBar () 17 | 18 | @property (nonatomic, strong) UIActivityIndicatorView *indicatorView; 19 | 20 | @property (nonatomic, assign) CGFloat pWidth; 21 | @property (nonatomic, assign) CGFloat eWidth; 22 | @property (nonatomic, assign) CGFloat oWidth; 23 | @property (nonatomic, assign) CGFloat dWidth; 24 | 25 | @end 26 | 27 | @implementation CLPickerToolBar 28 | 29 | - (void)setFontSize:(CGFloat)fontSize { 30 | _fontSize = fontSize; 31 | } 32 | 33 | - (void)setTitleColor:(UIColor *)titleColor { 34 | _titleColor = titleColor; 35 | if (_previewBtn) { 36 | [_previewBtn setTitleColor:[_titleColor colorWithAlphaComponent:CLBarEnabledAlpha] forState:UIControlStateNormal]; 37 | [_previewBtn setTitleColor:_titleColor forState:UIControlStateSelected]; 38 | } 39 | if (_editBtn) { 40 | [_editBtn setTitleColor:[_titleColor colorWithAlphaComponent:CLBarEnabledAlpha] forState:UIControlStateNormal]; 41 | [_editBtn setTitleColor:_titleColor forState:UIControlStateSelected]; 42 | } 43 | if (_originalBtn) { 44 | [_originalBtn setTitleColor:_titleColor forState:UIControlStateNormal]; 45 | } 46 | if (_tipLabel) { 47 | [_tipLabel setTitleColor:[_titleColor colorWithAlphaComponent:CLBarEnabledAlpha] forState:UIControlStateNormal]; 48 | } 49 | if (_doneBtn) { 50 | _doneBtn.titleColor = _titleColor; 51 | } 52 | if (_indicatorView) { 53 | _indicatorView.color = _titleColor; 54 | } 55 | } 56 | 57 | - (void)setEditSelect:(BOOL)editSelect { 58 | _editSelect = editSelect; 59 | if (_editBtn) { 60 | _editBtn.selected = _editSelect; 61 | } 62 | } 63 | 64 | - (void)startAnimating { 65 | [self.originalBtn setTitle:CLString(@"CLText_Original") forState:UIControlStateNormal]; 66 | [self.indicatorView startAnimating]; 67 | } 68 | 69 | - (void)stopAnimating { 70 | [self.indicatorView stopAnimating]; 71 | } 72 | 73 | - (void)layoutSubviews { 74 | [super layoutSubviews]; 75 | UIEdgeInsets inset = UIEdgeInsetsZero; 76 | if (@available(iOS 11, *)) { 77 | inset = self.safeAreaInsets; 78 | } 79 | if (_previewBtn) { 80 | _previewBtn.frame = CGRectMake(inset.left + itemMargin, 0, self.pWidth, CLToolBarHeight); 81 | } 82 | CGFloat previewRight = _previewBtn?(_previewBtn.right+itemSpacing):(inset.left + itemMargin); 83 | if (_editBtn) { 84 | _editBtn.frame = CGRectMake(previewRight, 0, self.eWidth, CLToolBarHeight); 85 | } 86 | if (_originalBtn) { 87 | _originalBtn.frame = CGRectMake(_editBtn?(_editBtn.right+itemSpacing):previewRight, 0, self.oWidth + 100, CLToolBarHeight); 88 | _indicatorView.center = CGPointMake(_originalBtn.left + self.oWidth + 28, CLToolBarHeight/2.0); 89 | } 90 | if (_doneBtn) { 91 | CGFloat doneWidth = self.dWidth + _fontSize + 2; 92 | if (_tipLabel) { 93 | _tipLabel.frame = CGRectMake(_editBtn?(_editBtn.right+itemSpacing-5):previewRight, 0, self.width - (_editBtn?(_editBtn.right+itemSpacing):previewRight) - doneWidth - 10, CLToolBarHeight); 94 | } 95 | _doneBtn.frame = CGRectMake(self.width - inset.right - itemMargin - doneWidth, 0, doneWidth, CLToolBarHeight); 96 | } 97 | } 98 | 99 | #pragma mark - 100 | #pragma mark -- Target Methods -- 101 | - (void)clickPreviewBtn:(UIButton *)sender { 102 | if (sender.selected) { 103 | if (self.clickPreviewBlock) { 104 | self.clickPreviewBlock(); 105 | } 106 | } 107 | } 108 | 109 | - (void)clickEditBtn:(UIButton *)sender { 110 | if (sender.selected) { 111 | if (self.clickEditBlock) { 112 | self.clickEditBlock(); 113 | } 114 | } 115 | } 116 | 117 | - (void)clickOriginalBtn:(UIButton *)sender { 118 | sender.selected = !sender.selected; 119 | if (self.clickOriginalBlock) { 120 | self.clickOriginalBlock(sender.selected); 121 | } 122 | } 123 | 124 | #pragma mark - 125 | #pragma mark -- Lazy Loads -- 126 | - (UIButton *)previewBtn { 127 | if (!_previewBtn) { 128 | _previewBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 129 | [_previewBtn setTitle:CLString(@"CLText_Preview") forState:UIControlStateNormal]; 130 | _previewBtn.titleLabel.adjustsFontSizeToFitWidth = YES; 131 | _previewBtn.titleLabel.font = [UIFont systemFontOfSize:_fontSize]; 132 | if (_titleColor) { 133 | [_previewBtn setTitleColor:[_titleColor colorWithAlphaComponent:CLBarEnabledAlpha] forState:UIControlStateNormal]; 134 | [_previewBtn setTitleColor:_titleColor forState:UIControlStateSelected]; 135 | } 136 | [_previewBtn addTarget:self action:@selector(clickPreviewBtn:) forControlEvents:UIControlEventTouchUpInside]; 137 | [self addSubview:_previewBtn]; 138 | } 139 | return _previewBtn; 140 | } 141 | 142 | - (UIButton *)editBtn { 143 | if (!_editBtn) { 144 | _editBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 145 | [_editBtn setTitle:CLString(@"CLText_Edit") forState:UIControlStateNormal]; 146 | if (_titleColor) { 147 | [_editBtn setTitleColor:[_titleColor colorWithAlphaComponent:CLBarEnabledAlpha] forState:UIControlStateNormal]; 148 | [_editBtn setTitleColor:_titleColor forState:UIControlStateSelected]; 149 | } 150 | _editBtn.titleLabel.adjustsFontSizeToFitWidth = YES; 151 | _editBtn.titleLabel.font = [UIFont systemFontOfSize:_fontSize]; 152 | [_editBtn addTarget:self action:@selector(clickEditBtn:) forControlEvents:UIControlEventTouchUpInside]; 153 | [self addSubview:_editBtn]; 154 | } 155 | return _editBtn; 156 | } 157 | 158 | - (UIButton *)originalBtn { 159 | if (!_originalBtn) { 160 | _originalBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 161 | [_originalBtn setImage:[UIImage imageNamedFromBundle:@"btn_original_unselected"] forState:UIControlStateNormal]; 162 | [_originalBtn setImage:[UIImage imageNamedFromBundle:@"btn_original_unselected"] forState:UIControlStateHighlighted]; 163 | [_originalBtn setImage:[UIImage imageNamedFromBundle:@"btn_original_selected"] forState:UIControlStateSelected]; 164 | [_originalBtn setImage:[UIImage imageNamedFromBundle:@"btn_original_selected"] forState:UIControlStateSelected|UIControlStateHighlighted]; 165 | [_originalBtn setTitle:CLString(@"CLText_Original") forState:UIControlStateNormal]; 166 | if (_titleColor) { 167 | [_originalBtn setTitleColor:_titleColor forState:UIControlStateNormal]; 168 | } 169 | _originalBtn.titleLabel.adjustsFontSizeToFitWidth = YES; 170 | _originalBtn.titleLabel.font = [UIFont systemFontOfSize:_fontSize]; 171 | _originalBtn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; 172 | [_originalBtn addTarget:self action:@selector(clickOriginalBtn:) forControlEvents:UIControlEventTouchUpInside]; 173 | [self addSubview:_originalBtn]; 174 | [self addSubview:self.indicatorView]; 175 | } 176 | return _originalBtn; 177 | } 178 | 179 | - (UIButton *)tipLabel { 180 | if (!_tipLabel) { 181 | _tipLabel = [UIButton buttonWithType:UIButtonTypeCustom]; 182 | _tipLabel.titleLabel.font = [UIFont systemFontOfSize:_fontSize]; 183 | _tipLabel.titleLabel.adjustsFontSizeToFitWidth = YES; 184 | _tipLabel.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; 185 | if (_titleColor) { 186 | [_tipLabel setTitleColor:[_titleColor colorWithAlphaComponent:CLBarEnabledAlpha] forState:UIControlStateNormal]; 187 | } 188 | _tipLabel.enabled = NO; 189 | [self addSubview:_tipLabel]; 190 | } 191 | return _tipLabel; 192 | } 193 | 194 | - (UIActivityIndicatorView *)indicatorView { 195 | if (!_indicatorView) { 196 | _indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 197 | _indicatorView.hidesWhenStopped = YES; 198 | if (_titleColor) { 199 | _indicatorView.color = _titleColor; 200 | } 201 | } 202 | return _indicatorView; 203 | } 204 | 205 | - (CLDoneButton *)doneBtn { 206 | if (!_doneBtn) { 207 | _doneBtn = [CLDoneButton buttonWithType:UIButtonTypeCustom]; 208 | if (_titleColor) { 209 | _doneBtn.titleColor = _titleColor; 210 | } 211 | [self addSubview:_doneBtn]; 212 | } 213 | return _doneBtn; 214 | } 215 | 216 | - (CGFloat)pWidth { 217 | if (!_pWidth) { 218 | _pWidth = GetMatchValue(CLString(@"CLText_Preview"), _fontSize, YES, CLToolBarHeight)?:0.01; 219 | } 220 | return _pWidth; 221 | } 222 | 223 | - (CGFloat)eWidth { 224 | if (!_eWidth) { 225 | _eWidth = GetMatchValue(CLString(@"CLText_Edit"), _fontSize, YES, CLToolBarHeight)?:0.01; 226 | } 227 | return _eWidth; 228 | } 229 | 230 | - (CGFloat)oWidth { 231 | if (!_oWidth) { 232 | _oWidth = GetMatchValue(CLString(@"CLText_Original"), _fontSize, YES, CLToolBarHeight)?:0.01; 233 | } 234 | return _oWidth; 235 | } 236 | 237 | - (CGFloat)dWidth { 238 | if (!_dWidth) { 239 | _dWidth = GetMatchValue(CLString(@"CLText_Done"), _fontSize, YES, CLToolBarHeight)?:0.01; 240 | } 241 | return _dWidth; 242 | } 243 | 244 | @end 245 | 246 | @interface CLDoneButton () 247 | 248 | @property (nonatomic, strong) UILabel *numberLabel; 249 | 250 | @end 251 | 252 | @implementation CLDoneButton 253 | 254 | - (instancetype)init { 255 | self = [super init]; 256 | if (self) { 257 | [self _setUp]; 258 | } 259 | return self; 260 | } 261 | 262 | - (instancetype)initWithFrame:(CGRect)frame { 263 | self = [super initWithFrame:frame]; 264 | if (self) { 265 | [self _setUp]; 266 | } 267 | return self; 268 | } 269 | 270 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 271 | self = [super initWithCoder:aDecoder]; 272 | if (self) { 273 | [self _setUp]; 274 | } 275 | return self; 276 | } 277 | 278 | - (void)_setUp { 279 | [self setTitle:CLString(@"CLText_Done") forState:UIControlStateNormal]; 280 | self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 281 | [self addTarget:self action:@selector(clickDoneBtn:) forControlEvents:UIControlEventTouchUpInside]; 282 | self.titleFontSize = CLToolBarTitleFontSize; 283 | } 284 | 285 | - (void)layoutSubviews { 286 | [super layoutSubviews]; 287 | self.numberLabel.frame = CGRectMake(0, (self.height - (_titleFontSize + 1))/2.0, (_titleFontSize + 1), _titleFontSize + 1); 288 | } 289 | 290 | - (void)setTitleFontSize:(CGFloat)titleFontSize { 291 | _titleFontSize = titleFontSize; 292 | self.titleLabel.font = [UIFont systemFontOfSize:_titleFontSize]; 293 | self.numberLabel.font = [UIFont systemFontOfSize:(_titleFontSize - 1)]; 294 | self.numberLabel.layer.cornerRadius = (_titleFontSize + 1)/2.0f; 295 | } 296 | 297 | - (void)setTitleColor:(UIColor *)titleColor { 298 | _titleColor = titleColor; 299 | [self setTitleColor:[_titleColor colorWithAlphaComponent:CLBarEnabledAlpha] forState:UIControlStateNormal]; 300 | [self setTitleColor:_titleColor forState:UIControlStateSelected]; 301 | } 302 | 303 | - (void)setNumberColor:(UIColor *)numberColor { 304 | _numberColor = numberColor; 305 | self.numberLabel.textColor = _numberColor; 306 | } 307 | 308 | - (void)setNumber:(NSInteger)number { 309 | if (_number != number) { 310 | _number = number; 311 | self.selected = _number > 0; 312 | if (_number) { 313 | self.numberLabel.hidden = NO; 314 | self.numberLabel.text = [NSString stringWithFormat:@"%ld", (long)number]; 315 | [UIView showOscillatoryAnimationWithLayer:_numberLabel.layer type:CLOscillatoryAnimationToSmaller]; 316 | } else { 317 | _numberLabel.hidden = YES; 318 | } 319 | } 320 | } 321 | 322 | - (void)clickDoneBtn:(CLDoneButton *)sender { 323 | if (sender.selected) { 324 | if (self.clickDoneBlock) { 325 | self.clickDoneBlock(); 326 | } 327 | } 328 | } 329 | 330 | #pragma mark - 331 | #pragma mark -- Lazy Loads -- 332 | - (UILabel *)numberLabel { 333 | if (!_numberLabel) { 334 | _numberLabel = [[UILabel alloc] init]; 335 | _numberLabel.backgroundColor = _titleColor?:CLBarItemTitleDefaultColor; 336 | _numberLabel.layer.masksToBounds = YES; 337 | _numberLabel.textAlignment = NSTextAlignmentCenter; 338 | _numberLabel.adjustsFontSizeToFitWidth = YES; 339 | _numberLabel.hidden = YES; 340 | [self addSubview:_numberLabel]; 341 | } 342 | return _numberLabel; 343 | } 344 | 345 | @end 346 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPreviewCollectioCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPreviewCollectioCell.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/7. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class CLPhotoModel; 13 | @class CLPreviewImageCell; 14 | @class CLPreviewVideoCell; 15 | @interface CLPreviewCollectioCell : UICollectionViewCell 16 | 17 | @property (nonatomic, assign) BOOL showGif; 18 | @property (nonatomic, assign) BOOL showLivePhoto; 19 | 20 | @property (nonatomic, strong) CLPhotoModel *model; 21 | @property (nonatomic, assign) BOOL willDisplaying; 22 | @property (nonatomic, copy) void (^singleTapCallBack)(void); 23 | 24 | - (void)reloadGif; 25 | 26 | - (void)pausePlay:(BOOL)stop; 27 | 28 | - (void)resetScale; 29 | 30 | @end 31 | 32 | @interface CLPreviewBaseCell : UIView 33 | 34 | @property (nonatomic, strong) UIImageView *imageView; 35 | @property (nonatomic, strong) UIActivityIndicatorView *indicator; 36 | @property (nonatomic, strong) PHAsset *asset; 37 | @property (nonatomic, assign) PHImageRequestID imageRequestID; 38 | @property (nonatomic, strong) UITapGestureRecognizer *singleTap; 39 | @property (nonatomic, strong) UITapGestureRecognizer *doubleTap; 40 | @property (nonatomic, copy) void (^singleTapCallBack)(void); 41 | 42 | - (void)singleTapAction; 43 | 44 | - (void)doubleTapAction:(UITapGestureRecognizer *)tap; 45 | 46 | - (void)loadAsset:(PHAsset *)asset; 47 | 48 | - (void)loadGifImage:(PHAsset *)asset; 49 | 50 | - (void)loadLivePhoto:(PHAsset *)asset; 51 | 52 | @end 53 | 54 | @interface CLPreviewImageCell : CLPreviewBaseCell 55 | 56 | @property (nonatomic, strong) PHLivePhotoView *livePhotoView API_AVAILABLE(ios(9.1)); 57 | @property (nonatomic, strong) UIView *containerView; 58 | @property (nonatomic, strong) UIScrollView *scrollView; 59 | 60 | - (void)resetScale; 61 | 62 | - (void)resumeGif; 63 | 64 | - (void)pauseGif; 65 | 66 | - (void)stopPlayLivePhoto; 67 | 68 | @end 69 | 70 | @interface CLPreviewVideoCell : CLPreviewBaseCell 71 | 72 | @property (nonatomic, strong) AVPlayerLayer *playLayer; 73 | @property (nonatomic, strong) AVPlayerItem *playerItem; 74 | @property (nonatomic, strong) UIImageView *icloudView; 75 | @property (nonatomic, strong) UIButton *playBtn; 76 | 77 | - (void)stopPlayVideo:(BOOL)stop; 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPreviewCollectioCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLPreviewCollectioCell.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/7. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLPreviewCollectioCell.h" 10 | #import "CLConfig.h" 11 | #import "CLExtHeader.h" 12 | 13 | @interface CLPreviewCollectioCell () 14 | 15 | @property (nonatomic, strong) CLPreviewImageCell *imageView; 16 | @property (nonatomic, strong) CLPreviewVideoCell *videoView; 17 | 18 | @end 19 | 20 | @implementation CLPreviewCollectioCell 21 | 22 | - (CLPreviewImageCell *)imageView { 23 | if (!_imageView) { 24 | _imageView = [[CLPreviewImageCell alloc] initWithFrame:self.bounds]; 25 | cl_WS(ws); 26 | [_imageView setSingleTapCallBack:^{ 27 | if (ws.singleTapCallBack) { 28 | ws.singleTapCallBack(); 29 | } 30 | }]; 31 | if (_videoView) { 32 | [_videoView removeFromSuperview]; 33 | _videoView = nil; 34 | } 35 | [self.contentView addSubview:_imageView]; 36 | } 37 | return _imageView; 38 | } 39 | 40 | - (CLPreviewVideoCell *)videoView { 41 | if (!_videoView) { 42 | _videoView = [[CLPreviewVideoCell alloc] initWithFrame:self.bounds]; 43 | cl_WS(ws); 44 | [_videoView setSingleTapCallBack:^{ 45 | if (ws.singleTapCallBack) { 46 | ws.singleTapCallBack(); 47 | } 48 | }]; 49 | if (_imageView) { 50 | [_imageView removeFromSuperview]; 51 | _imageView = nil; 52 | } 53 | [self.contentView addSubview:_videoView]; 54 | } 55 | return _videoView; 56 | } 57 | 58 | - (void)layoutSubviews { 59 | [super layoutSubviews]; 60 | _imageView.frame = self.bounds; 61 | _videoView.frame = self.bounds; 62 | } 63 | 64 | - (void)resetScale { 65 | [_imageView resetScale]; 66 | } 67 | 68 | - (void)reloadGif { 69 | if (self.willDisplaying) { 70 | self.willDisplaying = NO; 71 | [self reload]; 72 | } else { 73 | [self resumePlay]; 74 | } 75 | } 76 | 77 | - (void)setModel:(CLPhotoModel *)model { 78 | _model = model; 79 | if (_model.type == CLAssetMediaTypeVideo) { 80 | [self.videoView loadAsset:_model.asset]; 81 | } else { 82 | [self.imageView loadAsset:_model.asset]; 83 | } 84 | } 85 | 86 | - (void)reload { 87 | if (self.showGif && _model.type == CLAssetMediaTypeGif) { 88 | [self.imageView loadGifImage:self.model.asset]; 89 | } else if (self.showLivePhoto && _model.type == CLAssetMediaTypeLivePhoto) { 90 | [self.imageView loadLivePhoto:self.model.asset]; 91 | } 92 | } 93 | 94 | - (void)resumePlay { 95 | if (self.model.type == CLAssetMediaTypeGif) { 96 | [self.imageView resumeGif]; 97 | } 98 | } 99 | 100 | - (void)pausePlay:(BOOL)stop { 101 | if (self.model.type == CLAssetMediaTypeGif) { 102 | [self.imageView pauseGif]; 103 | } else if (self.model.type == CLAssetMediaTypeLivePhoto) { 104 | [self.imageView stopPlayLivePhoto]; 105 | } else if (self.model.type == CLAssetMediaTypeVideo) { 106 | [self.videoView stopPlayVideo:stop]; 107 | } 108 | } 109 | 110 | @end 111 | 112 | @implementation CLPreviewBaseCell 113 | 114 | #pragma mark - 115 | #pragma mark -- Initial Methods -- 116 | - (instancetype)initWithFrame:(CGRect)frame { 117 | if (self = [super initWithFrame:frame]) { 118 | [self _setUp]; 119 | } 120 | return self; 121 | } 122 | 123 | - (instancetype)init { 124 | self = [super init]; 125 | if (self) { 126 | [self _setUp]; 127 | } 128 | return self; 129 | } 130 | 131 | - (void)_setUp { 132 | [self.singleTap requireGestureRecognizerToFail:self.doubleTap]; 133 | } 134 | 135 | - (void)layoutSubviews { 136 | [super layoutSubviews]; 137 | self.indicator.center = self.center; 138 | } 139 | 140 | #pragma mark - 141 | #pragma mark -- Lazy Loads -- 142 | - (UIActivityIndicatorView *)indicator { 143 | if (!_indicator) { 144 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 145 | _indicator.hidesWhenStopped = YES; 146 | _indicator.center = self.center; 147 | } 148 | return _indicator; 149 | } 150 | 151 | - (UIImageView *)imageView { 152 | if (!_imageView) { 153 | _imageView = [[UIImageView alloc] init]; 154 | _imageView.contentMode = UIViewContentModeScaleAspectFit; 155 | } 156 | return _imageView; 157 | } 158 | 159 | - (UITapGestureRecognizer *)singleTap { 160 | if (!_singleTap) { 161 | _singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapAction)]; 162 | [self addGestureRecognizer:_singleTap]; 163 | } 164 | return _singleTap; 165 | } 166 | 167 | - (UITapGestureRecognizer *)doubleTap { 168 | if (!_doubleTap) { 169 | _doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapAction:)]; 170 | _doubleTap.numberOfTapsRequired = 2; 171 | [self addGestureRecognizer:_doubleTap]; 172 | } 173 | return _doubleTap; 174 | } 175 | 176 | #pragma mark - 177 | #pragma mark -- Target Methods -- 178 | - (void)singleTapAction { 179 | if (self.singleTapCallBack) self.singleTapCallBack(); 180 | } 181 | 182 | - (void)doubleTapAction:(UITapGestureRecognizer *)tap { 183 | } 184 | 185 | #pragma mark - 186 | #pragma mark -- Public Methods -- 187 | - (void)loadAsset:(PHAsset *)asset { 188 | 189 | } 190 | 191 | - (void)loadGifImage:(PHAsset *)asset { 192 | 193 | } 194 | 195 | - (void)loadLivePhoto:(PHAsset *)asset { 196 | 197 | } 198 | 199 | @end 200 | 201 | @interface CLPreviewImageCell () { 202 | BOOL _loaded; 203 | BOOL _isPlaying; 204 | } 205 | @end 206 | @implementation CLPreviewImageCell 207 | 208 | #pragma mark -- 重写父类方法 -- 209 | - (void)loadAsset:(PHAsset *)asset { 210 | if (self.asset.localIdentifier && [self.asset.localIdentifier isEqualToString:asset.localIdentifier]) { 211 | return; 212 | } 213 | if (self.asset && self.imageRequestID >= 0) { 214 | [[PHCachingImageManager defaultManager] cancelImageRequest:self.imageRequestID]; 215 | } 216 | self.asset = asset; 217 | if (_livePhotoView) { 218 | [_livePhotoView removeFromSuperview]; 219 | _livePhotoView = nil; 220 | } 221 | [self.indicator startAnimating]; 222 | CGFloat minWidth = MIN(self.asset.pixelWidth, self.width * [UIScreen mainScreen].scale); 223 | cl_weakSelf(self); 224 | self.imageRequestID = [CLPhotoShareManager requestCustomImageForAsset:self.asset size:CGSizeMake(minWidth, self.asset.pixelHeight/(self.asset.pixelWidth * 1.0) * minWidth) completion:^(UIImage *image, NSDictionary *info) { 225 | cl_strongSelf(weakSelf); 226 | if (!strongSelf) { 227 | return; 228 | } 229 | strongSelf.imageView.image = image; 230 | [strongSelf resetSubviewSize:asset]; 231 | if (![[info objectForKey:PHImageResultIsDegradedKey] boolValue]) { 232 | [strongSelf.indicator stopAnimating]; 233 | strongSelf->_loaded = YES; 234 | } 235 | }]; 236 | } 237 | 238 | - (void)loadGifImage:(PHAsset *)asset { 239 | if (_isPlaying) { 240 | return; 241 | } 242 | [self.indicator startAnimating]; 243 | cl_weakSelf(self); 244 | [CLPhotoShareManager requestOriginalImageDataForAsset:asset completion:^(NSData *data, NSDictionary *info) { 245 | cl_strongSelf(weakSelf); 246 | if (!strongSelf) { 247 | return; 248 | } 249 | if (![[info objectForKey:PHImageResultIsDegradedKey] boolValue]) { 250 | strongSelf.imageView.image = [CLPhotoManager transformToGifImageWithData:data]; 251 | [strongSelf resumeGif]; 252 | [strongSelf resetSubviewSize:asset]; 253 | [strongSelf.indicator stopAnimating]; 254 | } 255 | }]; 256 | } 257 | 258 | - (void)loadLivePhoto:(PHAsset *)asset { 259 | if (_isPlaying) { 260 | return; 261 | } 262 | if (@available(iOS 9.1, *)) { 263 | cl_weakSelf(self); 264 | [CLPhotoShareManager requestLivePhotoForAsset:asset completion:^(PHLivePhoto *lv, NSDictionary *info) { 265 | cl_strongSelf(weakSelf); 266 | if (!strongSelf) { 267 | return; 268 | } 269 | if (lv) { 270 | strongSelf.livePhotoView.livePhoto = lv; 271 | [strongSelf resetSubviewSize:asset]; 272 | [strongSelf.livePhotoView startPlaybackWithStyle:PHLivePhotoViewPlaybackStyleFull]; 273 | } 274 | }]; 275 | } 276 | } 277 | 278 | #pragma mark - 279 | #pragma mark -- Initial Methods -- 280 | - (instancetype)initWithFrame:(CGRect)frame { 281 | self = [super initWithFrame:frame]; 282 | if (self) { 283 | [self initUI]; 284 | } 285 | return self; 286 | } 287 | 288 | - (instancetype)init { 289 | self = [super init]; 290 | if (self) { 291 | [self initUI]; 292 | } 293 | return self; 294 | } 295 | 296 | - (void)initUI { 297 | [self addSubview:self.scrollView]; 298 | [self.scrollView addSubview:self.containerView]; 299 | [self.containerView addSubview:self.imageView]; 300 | [self addSubview:self.indicator]; 301 | } 302 | 303 | - (void)layoutSubviews { 304 | [super layoutSubviews]; 305 | self.scrollView.frame = self.bounds; 306 | [self.scrollView setZoomScale:1.0]; 307 | if (_loaded) { 308 | [self resetSubviewSize:self.asset?:self.imageView.image]; 309 | } 310 | _livePhotoView.frame = self.containerView.bounds; 311 | } 312 | 313 | #pragma mark - 314 | #pragma mark -- Lazy Loads -- 315 | - (UIScrollView *)scrollView { 316 | if (!_scrollView) { 317 | _scrollView = [[UIScrollView alloc] init]; 318 | _scrollView.frame = self.bounds; 319 | _scrollView.maximumZoomScale = 3.0; 320 | _scrollView.minimumZoomScale = 1.0; 321 | _scrollView.multipleTouchEnabled = YES; 322 | _scrollView.delegate = self; 323 | _scrollView.scrollsToTop = NO; 324 | _scrollView.showsHorizontalScrollIndicator = NO; 325 | _scrollView.showsVerticalScrollIndicator = NO; 326 | _scrollView.delaysContentTouches = NO; 327 | } 328 | return _scrollView; 329 | } 330 | 331 | - (UIView *)containerView { 332 | if (!_containerView) { 333 | _containerView = [[UIView alloc] init]; 334 | } 335 | return _containerView; 336 | } 337 | 338 | - (PHLivePhotoView *)livePhotoView { 339 | if (!_livePhotoView) { 340 | _livePhotoView = [[PHLivePhotoView alloc] initWithFrame:self.containerView.bounds]; 341 | _livePhotoView.contentMode = UIViewContentModeScaleAspectFit; 342 | _livePhotoView.delegate = self; 343 | [self.containerView addSubview:_livePhotoView]; 344 | } 345 | return _livePhotoView; 346 | } 347 | 348 | - (void)resetSubviewSize:(id)obj { 349 | CGRect frame = CGRectZero; 350 | UIImage *image = self.imageView.image; 351 | if (!image) { 352 | self.containerView.frame = CGRectZero; 353 | return; 354 | } 355 | CGFloat imageScale = image.size.width/image.size.height; 356 | CGFloat screenScale = self.width/self.height; 357 | CGPoint point = CGPointZero; 358 | if (self.height > self.width) { 359 | if (imageScale > screenScale) { 360 | frame.size.width = self.width; 361 | frame.size.height = self.width/imageScale; 362 | point = self.center; 363 | } else { 364 | frame.size.width = self.width; 365 | frame.size.height = self.width/imageScale; 366 | } 367 | } else { 368 | if (imageScale > screenScale) { 369 | frame.size.width = self.width; 370 | frame.size.height = self.width/imageScale; 371 | point = self.center; 372 | } else { 373 | if (image.size.height < self.height) { 374 | frame.size.height = self.height; 375 | frame.size.width = self.height*imageScale; 376 | point = self.center; 377 | } else { 378 | CGFloat scale = 1/4.0 * self.width / self.height; 379 | if (imageScale > scale) { 380 | frame.size.height = self.height; 381 | frame.size.width = self.height*imageScale; 382 | point = self.center; 383 | } else { 384 | frame.size.width = 1/3.0 * self.width; 385 | frame.size.height = 1/3.0 * self.width/imageScale; 386 | point = CGPointMake(self.center.x, frame.size.height/2.0); 387 | } 388 | } 389 | } 390 | } 391 | self.containerView.frame = frame; 392 | if (!CGPointEqualToPoint(point, CGPointZero)) { 393 | self.containerView.center = point; 394 | } 395 | CGSize contentSize = CGSizeMake(frame.size.width, frame.size.height); 396 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 397 | self.scrollView.contentSize = contentSize; 398 | self.imageView.frame = self.containerView.bounds; 399 | [self.scrollView scrollRectToVisible:self.bounds animated:NO]; 400 | }); 401 | } 402 | 403 | #pragma mark - 404 | #pragma mark -- Target Methods -- 405 | - (void)doubleTapAction:(UITapGestureRecognizer *)tap { 406 | [super doubleTapAction:tap]; 407 | UIScrollView *scrollView = self.scrollView; 408 | CGFloat scale = 1; 409 | if (scrollView.zoomScale != 3.0) { 410 | scale = 3; 411 | } else { 412 | scale = 1; 413 | } 414 | CGRect zoomRect = [self zoomRectForScale:scale withCenter:[tap locationInView:tap.view]]; 415 | [scrollView zoomToRect:zoomRect animated:YES]; 416 | } 417 | 418 | #pragma mark - 419 | #pragma mark -- PHLivePhotoViewDelegate -- 420 | - (void)livePhotoView:(PHLivePhotoView *)livePhotoView willBeginPlaybackWithStyle:(PHLivePhotoViewPlaybackStyle)playbackStyle API_AVAILABLE(ios(9.1)) { 421 | _isPlaying = YES; 422 | } 423 | 424 | - (void)livePhotoView:(PHLivePhotoView *)livePhotoView didEndPlaybackWithStyle:(PHLivePhotoViewPlaybackStyle)playbackStyle API_AVAILABLE(ios(9.1)) { 425 | _isPlaying = NO; 426 | } 427 | 428 | #pragma mark - 429 | #pragma mark -- UIScrollViewDelegate -- 430 | - (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center { 431 | CGRect zoomRect; 432 | zoomRect.size.height = self.scrollView.frame.size.height / scale; 433 | zoomRect.size.width = self.scrollView.frame.size.width / scale; 434 | zoomRect.origin.x = center.x - (zoomRect.size.width /2.0); 435 | zoomRect.origin.y = center.y - (zoomRect.size.height /2.0); 436 | return zoomRect; 437 | } 438 | 439 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { 440 | return scrollView.subviews[0]; 441 | } 442 | 443 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView { 444 | CGFloat offsetX = (scrollView.width > scrollView.contentSize.width) ? (scrollView.width - scrollView.contentSize.width) * 0.5 : 0.0; 445 | CGFloat offsetY = (scrollView.height > scrollView.contentSize.height) ? (scrollView.height - scrollView.contentSize.height) * 0.5 : 0.0; 446 | self.containerView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, scrollView.contentSize.height * 0.5 + offsetY); 447 | } 448 | 449 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 450 | [self resumeGif]; 451 | } 452 | 453 | #pragma mark - 454 | #pragma mark -- Public Methods -- 455 | - (void)resetScale { 456 | self.scrollView.zoomScale = 1; 457 | } 458 | 459 | - (void)resumeGif { 460 | CALayer *layer = self.imageView.layer; 461 | if (layer.speed != 0) return; 462 | _isPlaying = YES; 463 | CFTimeInterval pausedTime = [layer timeOffset]; 464 | layer.speed = 1.0; 465 | layer.timeOffset = 0.0; 466 | layer.beginTime = 0.0; 467 | CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; 468 | layer.beginTime = timeSincePause; 469 | } 470 | 471 | - (void)pauseGif { 472 | CALayer *layer = self.imageView.layer; 473 | if (layer.speed == .0) return; 474 | CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; 475 | layer.speed = 0.0; 476 | layer.timeOffset = pausedTime; 477 | _isPlaying = NO; 478 | } 479 | 480 | - (void)stopPlayLivePhoto { 481 | _isPlaying = NO; 482 | if (@available(iOS 9.1, *)) { 483 | [_livePhotoView stopPlayback]; 484 | } else { 485 | // Fallback on earlier versions 486 | } 487 | } 488 | 489 | @end 490 | 491 | @implementation CLPreviewVideoCell 492 | 493 | - (void)loadAsset:(PHAsset *)asset { 494 | if (self.asset && self.imageRequestID >= 0) { 495 | [[PHCachingImageManager defaultManager] cancelImageRequest:self.imageRequestID]; 496 | } 497 | self.asset = asset; 498 | [self clearPlayer]; 499 | self.imageView.image = nil; 500 | self.icloudView.hidden = YES; 501 | self.playBtn.enabled = YES; 502 | self.imageView.hidden = NO; 503 | 504 | [self.indicator startAnimating]; 505 | CGFloat minWidth = MIN(self.asset.pixelWidth, self.width * [UIScreen mainScreen].scale); 506 | cl_weakSelf(self); 507 | self.imageRequestID = [CLPhotoShareManager requestCustomImageForAsset:self.asset size:CGSizeMake(minWidth, self.asset.pixelHeight/(self.asset.pixelWidth * 1.0) * minWidth) completion:^(UIImage *image, NSDictionary *info) { 508 | cl_strongSelf(weakSelf); 509 | if (!strongSelf) { 510 | return; 511 | } 512 | strongSelf.imageView.image = image; 513 | if (![[info objectForKey:PHImageResultIsDegradedKey] boolValue]) { 514 | strongSelf.icloudView.hidden = YES; 515 | [strongSelf.indicator stopAnimating]; 516 | } 517 | }]; 518 | } 519 | 520 | - (AVPlayerLayer *)playLayer { 521 | if (!_playLayer) { 522 | _playLayer = [[AVPlayerLayer alloc] init]; 523 | _playLayer.frame = self.bounds; 524 | } 525 | return _playLayer; 526 | } 527 | 528 | - (UIImageView *)icloudView { 529 | if (!_icloudView) { 530 | _icloudView = [[UIImageView alloc] initWithFrame:CGRectZero]; 531 | _icloudView.image = [UIImage imageNamedFromBundle:@"clicon_icloud_load"]; 532 | _icloudView.hidden = YES; 533 | [self addSubview:_icloudView]; 534 | } 535 | return _icloudView; 536 | } 537 | 538 | - (UIButton *)playBtn { 539 | if (!_playBtn) { 540 | _playBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 541 | [_playBtn setImage:[UIImage imageNamedFromBundle:@"btn_preview_play"] forState:UIControlStateNormal]; 542 | _playBtn.frame = CGRectMake(0, 0, 60, 60); 543 | [_playBtn addTarget:self action:@selector(playBtnClick) forControlEvents:UIControlEventTouchUpInside]; 544 | } 545 | [self bringSubviewToFront:_playBtn]; 546 | return _playBtn; 547 | } 548 | 549 | - (instancetype)initWithFrame:(CGRect)frame { 550 | self = [super initWithFrame:frame]; 551 | if (self) { 552 | [self initUI]; 553 | } 554 | return self; 555 | } 556 | 557 | - (instancetype)init { 558 | self = [super init]; 559 | if (self) { 560 | [self initUI]; 561 | } 562 | return self; 563 | } 564 | 565 | - (void)initUI { 566 | [self addSubview:self.imageView]; 567 | [self addSubview:self.icloudView]; 568 | [self addSubview:self.playBtn]; 569 | [self addSubview:self.indicator]; 570 | } 571 | 572 | - (void)layoutSubviews { 573 | [super layoutSubviews]; 574 | self.imageView.frame = self.bounds; 575 | if (@available(iOS 11.0, *)) { 576 | _icloudView.frame = CGRectMake(20, self.height - self.safeAreaInsets.bottom - CLToolBarHeight - 40, 20, 20); 577 | } else { 578 | _icloudView.frame = CGRectMake(20, self.height - CLToolBarHeight - 40, 20, 20); 579 | } 580 | _playBtn.center = self.center; 581 | _playLayer.frame = self.bounds; 582 | } 583 | 584 | - (void)singleTapAction { 585 | [super singleTapAction]; 586 | if (_playLayer && _playLayer.player) { 587 | if (_playLayer.player.rate) { 588 | self.playBtn.hidden = NO; 589 | [_playLayer.player pause]; 590 | } 591 | } 592 | } 593 | 594 | - (void)doubleTapAction:(UITapGestureRecognizer *)tap { 595 | [super doubleTapAction:tap]; 596 | [self setVideoStatus]; 597 | } 598 | 599 | - (void)setVideoStatus { 600 | if (!_playLayer) { 601 | cl_weakSelf(self); 602 | [CLPhotoShareManager requestVideoAssetForAsset:self.asset completion:^(AVAsset *asset, NSDictionary *info) { 603 | cl_strongSelf(weakSelf); 604 | if (!strongSelf) { 605 | return; 606 | } 607 | if ([[NSThread currentThread] isMainThread]) { 608 | [strongSelf loadVideoWithAsset:asset]; 609 | } else { 610 | dispatch_async(dispatch_get_main_queue(), ^{ 611 | [strongSelf loadVideoWithAsset:asset]; 612 | }); 613 | } 614 | }]; 615 | } else { 616 | [self switchVideoStatus]; 617 | } 618 | } 619 | 620 | - (void)loadVideoWithAsset:(AVAsset *)asset { 621 | if (!asset || ![asset isKindOfClass:[AVAsset class]]) { 622 | [self initVideoLoadFailedFromiCloudUI]; 623 | return; 624 | } 625 | self.playerItem = [AVPlayerItem playerItemWithAsset:asset]; 626 | if (!self.playerItem || ![self.playerItem isKindOfClass:[AVPlayerItem class]]) { 627 | [self initVideoLoadFailedFromiCloudUI]; 628 | return; 629 | } 630 | self.icloudView.hidden = YES; 631 | AVPlayer *player = [AVPlayer playerWithPlayerItem:self.playerItem]; 632 | [self.layer addSublayer:self.playLayer]; 633 | _playLayer.player = player; 634 | [self switchVideoStatus]; 635 | [_playLayer addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; 636 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:player.currentItem]; 637 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(enterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; 638 | } 639 | 640 | - (void)initVideoLoadFailedFromiCloudUI { 641 | self.icloudView.hidden = NO; 642 | self.playBtn.enabled = NO; 643 | } 644 | 645 | - (void)playBtnClick { 646 | [self doubleTapAction:nil]; 647 | } 648 | 649 | - (void)switchVideoStatus { 650 | AVPlayer *player = _playLayer.player; 651 | if (!player) return; 652 | if (!player.currentItem) return; 653 | CMTime stop = player.currentItem.currentTime; 654 | CMTime duration = player.currentItem.duration; 655 | if (player.rate == .0) { 656 | self.playBtn.hidden = YES; 657 | if (stop.value >= (duration.value-0.5)) { 658 | [player.currentItem seekToTime:CMTimeMake(0, 1)]; 659 | } 660 | [player play]; 661 | } else { 662 | self.playBtn.hidden = NO; 663 | [player pause]; 664 | } 665 | } 666 | 667 | - (void)enterBackground { 668 | self.playBtn.hidden = NO; 669 | [_playLayer.player pause]; 670 | } 671 | 672 | - (void)playFinished:(AVPlayerItem *)item { 673 | [super doubleTapAction:nil]; 674 | self.playBtn.hidden = NO; 675 | self.imageView.hidden = NO; 676 | [_playLayer.player seekToTime:kCMTimeZero]; 677 | } 678 | 679 | //监听获得消息 680 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 681 | if (self.playerItem && object == self.playerItem) { 682 | if ([keyPath isEqualToString:@"status"]) { 683 | if (self.playerItem.status == AVPlayerStatusReadyToPlay) { 684 | self.imageView.hidden = YES; 685 | } 686 | } 687 | } else { 688 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 689 | } 690 | } 691 | 692 | - (void)stopPlayVideo:(BOOL)stop { 693 | if (!_playLayer) { 694 | return; 695 | } 696 | AVPlayer *player = _playLayer.player; 697 | if (!stop) { 698 | [player pause]; 699 | self.playBtn.hidden = NO; 700 | return; 701 | } 702 | if (player.rate != .0) { 703 | [player pause]; 704 | self.playBtn.hidden = NO; 705 | [self clearPlayer]; 706 | } 707 | } 708 | 709 | - (void)dealloc { 710 | [self clearPlayer]; 711 | } 712 | 713 | - (void)clearPlayer { 714 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 715 | if (_playLayer) { 716 | _playerItem = nil; 717 | _playLayer.player = nil; 718 | [_playLayer removeFromSuperlayer]; 719 | [_playLayer removeObserver:self forKeyPath:@"status"]; 720 | _playLayer = nil; 721 | } 722 | } 723 | 724 | @end 725 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPreviewViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPreviewViewController.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/7. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CLPhotoModel; 12 | @interface CLPreviewViewController : UIViewController 13 | 14 | @property (nonatomic, assign) NSInteger currentIndex; 15 | @property (nonatomic, strong) CLPhotoModel *currentModel; 16 | @property (nonatomic, strong) NSArray *photoArray; 17 | 18 | @property (nonatomic, copy) void(^didReloadToolBarStatus)(BOOL reloadList); 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLPreviewViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLPreviewViewController.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/7. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLPreviewViewController.h" 10 | #import "CLPickerRootController.h" 11 | #import "CLEditVideoController.h" 12 | #import "CLEditImageController.h" 13 | #import "CLPreviewCollectioCell.h" 14 | #import "CLPickerToolBar.h" 15 | #import "CLConfig.h" 16 | #import "CLExtHeader.h" 17 | 18 | static CGFloat minimumLineSpacing = 20.0f; 19 | static NSString *itemIdentifier = @"CLPreviewCollectioCellItemIdentifier"; 20 | @interface CLPreviewViewController () { 21 | UICollectionViewFlowLayout *_layout; 22 | BOOL _hideBar; 23 | CLPhotoModel *_currentModel; 24 | } 25 | 26 | @property (nonatomic, strong) UIButton *rightItem; 27 | @property (nonatomic, strong) UICollectionView *collectionView; 28 | @property (nonatomic, strong) CLPickerToolBar *toolBar; 29 | @property (nonatomic, assign) NSInteger currentPage; 30 | 31 | @end 32 | 33 | @implementation CLPreviewViewController 34 | 35 | - (CLPickerRootController *)picker { 36 | return (CLPickerRootController *)self.navigationController; 37 | } 38 | 39 | - (void)viewDidLoad { 40 | [super viewDidLoad]; 41 | self.automaticallyAdjustsScrollViewInsets = NO; 42 | [self _initNavigationItems]; 43 | self.view.layer.masksToBounds = YES; 44 | self.collectionView.backgroundColor = [UIColor blackColor]; 45 | self.currentPage = self.currentIndex; 46 | [self refreshNavigationBar]; 47 | self.toolBar.hidden = NO; 48 | } 49 | 50 | - (void)viewWillAppear:(BOOL)animated { 51 | [super viewWillAppear:animated]; 52 | } 53 | 54 | - (void)viewDidAppear:(BOOL)animated { 55 | [super viewDidAppear:animated]; 56 | [self play]; 57 | } 58 | 59 | - (void)viewWillLayoutSubviews { 60 | [super viewWillLayoutSubviews]; 61 | [UIApplication sharedApplication].statusBarHidden = _hideBar; 62 | self.navigationController.navigationBar.hidden = _hideBar; 63 | UIEdgeInsets inset = UIEdgeInsetsZero; 64 | if (@available(iOS 11, *)) { 65 | inset = self.view.safeAreaInsets; 66 | } 67 | _toolBar.frame = CGRectMake(0, self.view.height - CLToolBarHeight - inset.bottom, self.view.width, CLToolBarHeight + inset.bottom); 68 | _layout.itemSize = CGSizeMake(self.view.width, self.view.height); 69 | [_collectionView setCollectionViewLayout:_layout]; 70 | _collectionView.frame = CGRectMake(-minimumLineSpacing/2.0, 0, minimumLineSpacing+self.view.width, self.view.height); 71 | [_collectionView setContentOffset:CGPointMake((self.view.width+minimumLineSpacing)*_currentIndex, 0)]; 72 | } 73 | 74 | - (void)_initNavigationItems { 75 | UIButton *leftItem = [UIButton buttonWithType:UIButtonTypeCustom]; 76 | leftItem.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; 77 | leftItem.frame = CGRectMake(0, 0, self.navigationController.navigationBar.height, self.navigationController.navigationBar.height); 78 | [leftItem setImage:[UIImage imageNamedFromBundle:@"btn_backItem_icon"] forState:UIControlStateNormal]; 79 | [leftItem addTarget:self action:@selector(clickCancelItemAction) forControlEvents:UIControlEventTouchUpInside]; 80 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:leftItem]; 81 | if (self.picker.allowImgMultiple) { 82 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.rightItem]; 83 | } 84 | } 85 | 86 | #pragma mark - 87 | #pragma mark -- Target Methods -- 88 | - (void)clickCancelItemAction { 89 | [self.navigationController popViewControllerAnimated:YES]; 90 | } 91 | 92 | - (void)clickRightItemAction:(UIButton *)sender { 93 | if (_photoArray.count > _currentIndex) { 94 | CLPhotoModel *model = _photoArray[_currentIndex]; 95 | if (!sender.selected) { 96 | // ToDo : 检测选择个数 97 | if (self.picker.selectedModels.count < self.picker.maxSelectCount) { 98 | model.isSelected = YES; 99 | if (![CLPhotoManager checkSelcectedWithModel:model identifiers:[CLPhotoManager getLocalIdentifierArrayWithArray:self.picker.selectedModels]]) { 100 | [self.picker.selectedModels addObject:model]; 101 | } 102 | } else { 103 | [self.picker showText:[NSString stringWithFormat:CLString(@"CLText_MaxImagesCount"), self.picker.maxSelectCount]]; 104 | return; 105 | } 106 | } else { 107 | model.isSelected = NO; 108 | NSArray *selectedModels = [NSArray arrayWithArray:self.picker.selectedModels]; 109 | [selectedModels enumerateObjectsUsingBlock:^(CLPhotoModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 110 | if ([model.asset.localIdentifier isEqualToString:obj.asset.localIdentifier]) { 111 | [self.picker.selectedModels removeObject:obj]; 112 | } 113 | }]; 114 | } 115 | } 116 | [self refreshBottomToolBarReloadList:YES]; 117 | sender.selected = !sender.selected; 118 | if (sender.selected) { 119 | [UIView showOscillatoryAnimationWithLayer:sender.layer type:CLOscillatoryAnimationToBigger]; 120 | } 121 | } 122 | 123 | - (void)clickDoneItemAction { 124 | if (_currentPage < _photoArray.count) { 125 | CLPreviewCollectioCell *cell = (CLPreviewCollectioCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:_currentPage inSection:0]]; 126 | if (cell) { 127 | [cell pausePlay:YES]; 128 | } 129 | } 130 | if (_currentModel.type == CLAssetMediaTypeVideo) { 131 | [self.picker showProgress]; 132 | cl_weakSelf(self); 133 | [CLPhotoShareManager requestVideoAssetForAsset:_currentModel.asset completion:^(AVAsset *asset, NSDictionary *info) { 134 | cl_strongSelf(weakSelf); 135 | if (!strongSelf) { 136 | return; 137 | } 138 | if (!asset) { 139 | [strongSelf.picker showText:CLString(@"CLText_NotGetVideoInfo")]; 140 | return; 141 | } 142 | NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; 143 | if([tracks count] > 0) { 144 | if ([[tracks firstObject] isKindOfClass:[AVCompositionTrack class]]) { 145 | [strongSelf.picker showText:CLString(@"CLText_UnableToDecode")]; 146 | } else { 147 | [strongSelf.picker clickPickingVideoActionForAsset:asset 148 | range:CMTimeRangeMake(kCMTimeZero, asset.duration) 149 | degrees:0]; 150 | } 151 | } else { 152 | [strongSelf.picker showText:CLString(@"CLText_NotGetVideoInfo")]; 153 | } 154 | }]; 155 | } else { 156 | if (self.picker.allowImgMultiple) { 157 | [self.picker didFinishPickingPhotosAction]; 158 | } else { 159 | if (_photoArray.count > _currentIndex) { 160 | CLPhotoModel *model = _photoArray[_currentIndex]; 161 | if (![CLPhotoManager checkSelcectedWithModel:model identifiers:[CLPhotoManager getLocalIdentifierArrayWithArray:self.picker.selectedModels]]) { 162 | [self.picker.selectedModels addObject:model]; 163 | [self.picker didFinishPickingPhotosAction]; 164 | } 165 | } 166 | } 167 | } 168 | } 169 | 170 | - (void)clickEditAction { 171 | if (_currentPage < _photoArray.count) { 172 | CLPreviewCollectioCell *cell = (CLPreviewCollectioCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:_currentPage inSection:0]]; 173 | if (cell) { 174 | [cell pausePlay:YES]; 175 | } 176 | } 177 | if (_currentModel.type == CLAssetMediaTypeVideo) { 178 | CLEditVideoController *edit = [[CLEditVideoController alloc] init]; 179 | edit.model = _currentModel; 180 | [self.navigationController pushViewController:edit animated:NO]; 181 | } else { 182 | CLEditImageController *image = [[CLEditImageController alloc] init]; 183 | image.model = _currentModel; 184 | [self.navigationController pushViewController:image animated:NO]; 185 | } 186 | } 187 | 188 | #pragma mark - 189 | #pragma mark -- UICollectionViewDataSource & UICollectionViewDelegate -- 190 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 191 | return _photoArray.count; 192 | } 193 | 194 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 195 | CLPreviewCollectioCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:itemIdentifier forIndexPath:indexPath]; 196 | cell.showGif = self.picker.allowSelectGif; 197 | cell.showLivePhoto = self.picker.allowSelectLivePhoto; 198 | if (_photoArray.count > indexPath.row) { 199 | cell.model = _photoArray[indexPath.row]; 200 | } 201 | cl_weakSelf(self); 202 | cell.singleTapCallBack = ^() { 203 | cl_strongSelf(weakSelf); 204 | [strongSelf handlerSingleTap]; 205 | }; 206 | return cell; 207 | } 208 | 209 | - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { 210 | [(CLPreviewCollectioCell *)cell resetScale]; 211 | ((CLPreviewCollectioCell *)cell).willDisplaying = YES; 212 | } 213 | 214 | - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { 215 | [(CLPreviewCollectioCell *)cell pausePlay:YES]; 216 | } 217 | 218 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 219 | } 220 | 221 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { 222 | return minimumLineSpacing; 223 | } 224 | 225 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { 226 | return 0; 227 | } 228 | 229 | //- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 230 | // return CGSizeMake(self.view.width, self.view.height); 231 | //} 232 | 233 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 234 | return UIEdgeInsetsMake(0, minimumLineSpacing/2.0, 0, minimumLineSpacing/2.0); 235 | } 236 | 237 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 238 | if (scrollView == (UIScrollView *)_collectionView) { 239 | CGPoint offSet = scrollView.contentOffset; 240 | _currentPage = (offSet.x + ((minimumLineSpacing + self.view.width) * 0.5)) / (minimumLineSpacing + self.view.width); 241 | [self refreshNavigationBar]; 242 | } 243 | } 244 | 245 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 246 | CGFloat page = scrollView.contentOffset.x/(minimumLineSpacing + self.view.width); 247 | if (ceilf(page) >= _photoArray.count) { 248 | return; 249 | } 250 | _currentIndex = [[NSString stringWithFormat:@"%.0f", page] integerValue]; 251 | [self play]; 252 | } 253 | 254 | #pragma mark - 255 | #pragma mark -- Private Methods -- 256 | - (void)play { 257 | if (_photoArray.count > _currentIndex) { 258 | CLPhotoModel *model = _photoArray[_currentIndex]; 259 | if (model.type == CLAssetMediaTypeGif || 260 | model.type == CLAssetMediaTypeLivePhoto) { 261 | if ([_collectionView numberOfItemsInSection:0] > _currentIndex) { 262 | CLPreviewCollectioCell *cell = (CLPreviewCollectioCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:_currentIndex inSection:0]]; 263 | cell.willDisplaying = YES; 264 | [cell reloadGif]; 265 | } 266 | } 267 | } 268 | } 269 | 270 | - (void)handlerSingleTap { 271 | _hideBar = !_hideBar; 272 | [UIApplication sharedApplication].statusBarHidden = _hideBar; 273 | self.navigationController.navigationBar.hidden = _hideBar; 274 | _toolBar.hidden = _hideBar; 275 | } 276 | 277 | - (void)refreshNavigationBar { 278 | if (_photoArray.count > _currentPage) { 279 | CLPhotoModel *model = _photoArray[_currentPage]; 280 | if ([model.asset.localIdentifier isEqualToString:_currentModel.asset.localIdentifier]) { 281 | return; 282 | } 283 | _currentModel = model; 284 | self.rightItem.selected = model.isSelected; 285 | //改变导航标题 286 | self.title = [NSString stringWithFormat:@"%ld/%ld", (long)(_currentPage + 1), (long)_photoArray.count]; 287 | [self switchToVideo]; 288 | } 289 | } 290 | 291 | - (BOOL)switchToVideo { 292 | if (_currentModel.type == CLAssetMediaTypeVideo) { 293 | if (self.picker.allowSelectOriginalImage) { 294 | _toolBar.originalBtn.hidden = YES; 295 | } 296 | self.rightItem.hidden = YES; 297 | _toolBar.doneBtn.number = 0; 298 | 299 | _toolBar.tipLabel.hidden = NO; 300 | if (self.picker.minDuration > _currentModel.duration) { 301 | [_toolBar.tipLabel setTitle:[NSString stringWithFormat:CLString(@"CLText_VideoLengthLeast"), @(self.picker.minDuration)] forState:UIControlStateNormal]; 302 | _toolBar.tipLabel.hidden = NO; 303 | _toolBar.doneBtn.selected = NO; 304 | _toolBar.editSelect = NO; 305 | } else if (self.picker.maxDuration < _currentModel.duration) { 306 | [_toolBar.tipLabel setTitle:[NSString stringWithFormat:CLString(@"CLText_VideoMaximumLength"), @(self.picker.maxDuration)] forState:UIControlStateNormal]; 307 | _toolBar.tipLabel.hidden = NO; 308 | _toolBar.doneBtn.selected = NO; 309 | _toolBar.editSelect = YES; 310 | } else { 311 | _toolBar.doneBtn.selected = YES; 312 | _toolBar.tipLabel.hidden = YES; 313 | _toolBar.editSelect = YES; 314 | } 315 | return YES; 316 | } 317 | if (self.rightItem.hidden) { 318 | self.rightItem.hidden = NO; 319 | } 320 | if (_toolBar) { 321 | _toolBar.tipLabel.hidden = YES; 322 | if (self.picker.allowSelectOriginalImage) { 323 | if (_toolBar.originalBtn.hidden) { 324 | _toolBar.originalBtn.hidden = NO; 325 | } 326 | } 327 | if (self.picker.selectedModels.count == 0) { 328 | if (self.picker.allowImgMultiple) { 329 | _toolBar.doneBtn.selected = NO; 330 | } else { 331 | _toolBar.doneBtn.selected = YES; 332 | } 333 | } 334 | if (_toolBar.doneBtn.number != self.picker.selectedModels.count) { 335 | _toolBar.doneBtn.number = self.picker.selectedModels.count; 336 | } 337 | _toolBar.editSelect = self.picker.allowEditImage; 338 | } 339 | return NO; 340 | } 341 | 342 | - (void)refreshBottomToolBarReloadList:(BOOL)reload { 343 | [self refreshBottomToolBarStatus]; 344 | if (self.didReloadToolBarStatus) { 345 | self.didReloadToolBarStatus(reload); 346 | } 347 | } 348 | 349 | - (void)refreshBottomToolBarStatus { 350 | if ([self switchToVideo]) return; 351 | if (self.picker.allowSelectOriginalImage) { 352 | _toolBar.originalBtn.hidden = NO; 353 | _toolBar.originalBtn.selected = self.picker.selectedOriginalImage; 354 | if (self.picker.selectedModels.count) { 355 | if (_toolBar.originalBtn.selected) { 356 | [self getOriginalImageBytes]; 357 | } else { 358 | [_toolBar.originalBtn setTitle:CLString(@"CLText_Original") forState:UIControlStateNormal]; 359 | } 360 | } else { 361 | [_toolBar.originalBtn setTitle:CLString(@"CLText_Original") forState:UIControlStateNormal]; 362 | } 363 | } 364 | if (_toolBar.doneBtn.number != self.picker.selectedModels.count) { 365 | _toolBar.doneBtn.number = self.picker.selectedModels.count; 366 | } 367 | } 368 | 369 | - (void)getOriginalImageBytes { 370 | [_toolBar startAnimating]; 371 | CLPickerRootController *pk = self.picker; 372 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 373 | cl_weakSelf(self); 374 | [CLPhotoManager getPhotosBytesWithArray:pk.selectedModels completion:^(NSString *photosBytes) { 375 | cl_strongSelf(weakSelf); 376 | if (!strongSelf) { 377 | return; 378 | } 379 | [strongSelf performSelector:@selector(setOriginalImageBytes:) withObject:photosBytes afterDelay:0.2]; 380 | }]; 381 | }); 382 | } 383 | 384 | - (void)setOriginalImageBytes:(id)object { 385 | cl_weakSelf(self); 386 | dispatch_async(dispatch_get_main_queue(), ^{ 387 | [weakSelf.toolBar stopAnimating]; 388 | [weakSelf.toolBar.originalBtn setTitle:[NSString stringWithFormat:@"%@(%@)", CLString(@"CLText_Original"), object] forState:UIControlStateNormal]; 389 | }); 390 | } 391 | 392 | #pragma mark - 393 | #pragma mark -- Lazy Loads -- 394 | - (UIButton *)rightItem { 395 | if (!_rightItem) { 396 | _rightItem = [UIButton buttonWithType:UIButtonTypeCustom]; 397 | _rightItem.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 398 | _rightItem.frame = CGRectMake(0, 0, self.navigationController.navigationBar.height, self.navigationController.navigationBar.height); 399 | [_rightItem setImage:[UIImage imageNamedFromBundle:@"btn_photo_unselected"] forState:UIControlStateNormal]; 400 | [_rightItem setImage:[UIImage imageNamedFromBundle:@"btn_photo_unselected"] forState:UIControlStateHighlighted]; 401 | [_rightItem setImage:[UIImage imageNamedFromBundle:@"btn_photo_selected"] forState:UIControlStateSelected]; 402 | [_rightItem setImage:[UIImage imageNamedFromBundle:@"btn_photo_selected"] forState:UIControlStateSelected|UIControlStateHighlighted]; 403 | [_rightItem addTarget:self action:@selector(clickRightItemAction:) forControlEvents:UIControlEventTouchUpInside]; 404 | } 405 | return _rightItem; 406 | } 407 | 408 | - (UICollectionView *)collectionView { 409 | if (!_collectionView) { 410 | _layout = [[UICollectionViewFlowLayout alloc] init]; 411 | _layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 412 | _collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:_layout]; 413 | _collectionView.delegate = self; 414 | _collectionView.dataSource = self; 415 | _collectionView.scrollsToTop = NO; 416 | _collectionView.pagingEnabled = YES; 417 | _collectionView.showsHorizontalScrollIndicator = NO; 418 | if (@available(iOS 11.0, *)) { 419 | _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 420 | } 421 | [_collectionView registerClass:[CLPreviewCollectioCell class] forCellWithReuseIdentifier:itemIdentifier]; 422 | [self.view addSubview:_collectionView]; 423 | } 424 | return _collectionView; 425 | } 426 | 427 | - (CLPickerToolBar *)toolBar { 428 | if (!_toolBar) { 429 | _toolBar = [[CLPickerToolBar alloc] init]; 430 | _toolBar.titleColor = self.picker.toolBarItemColor?:self.picker.navigationItemColor; 431 | _toolBar.fontSize = CLToolBarTitleFontSize; 432 | _toolBar.backgroundColor = [(self.picker.toolBarBackgroundColor?:self.picker.navigationColor) colorWithAlphaComponent:CLToolBarAlpha]; 433 | cl_WS(ws); 434 | if (!(!self.picker.allowEditImage && self.picker.selectMode == CLPickerSelectModeAllowImage)) { 435 | _toolBar.editBtn.hidden = NO; 436 | [_toolBar setClickEditBlock:^{ 437 | [ws clickEditAction]; 438 | }]; 439 | } 440 | if (self.picker.allowSelectOriginalImage) { 441 | _toolBar.originalBtn.selected = self.picker.selectedOriginalImage; 442 | _toolBar.originalBtn.hidden = NO; 443 | [_toolBar setClickOriginalBlock:^(BOOL selected) { 444 | ws.picker.selectedOriginalImage = selected; 445 | [ws refreshBottomToolBarReloadList:NO]; 446 | }]; 447 | } 448 | _toolBar.tipLabel.hidden = YES; 449 | _toolBar.doneBtn.hidden = NO; 450 | _toolBar.doneBtn.numberColor = CLSeletedNumberColor; 451 | [_toolBar.doneBtn setClickDoneBlock:^{ 452 | [ws clickDoneItemAction]; 453 | }]; 454 | [self refreshBottomToolBarStatus]; 455 | [self.view addSubview:_toolBar]; 456 | } 457 | return _toolBar; 458 | } 459 | 460 | - (void)dealloc { 461 | [self.picker cancelExport]; 462 | } 463 | 464 | - (void)didReceiveMemoryWarning { 465 | [super didReceiveMemoryWarning]; 466 | } 467 | 468 | @end 469 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLTouchViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLTouchViewController.h 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/18. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CLPhotoModel; 12 | @interface CLTouchViewController : UIViewController 13 | 14 | @property (nonatomic, assign) NSInteger index; 15 | 16 | @property (nonatomic, strong) CLPhotoModel *model; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CLPhotoLib/Classes/Lib/others/CLTouchViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLTouchViewController.m 3 | // CLPhotoLib 4 | // 5 | // Created by ClaudeLi on 2017/11/18. 6 | // Copyright © 2017年 ClaudeLi. All rights reserved. 7 | // 8 | 9 | #import "CLTouchViewController.h" 10 | #import "CLPhotoModel.h" 11 | #import "CLPhotoManager.h" 12 | #import "CLConfig.h" 13 | #import 14 | 15 | @interface CLTouchViewController () 16 | 17 | @end 18 | 19 | @implementation CLTouchViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | self.view.backgroundColor = [UIColor colorWithWhite:.8 alpha:.5]; 24 | [self setupUI]; 25 | } 26 | 27 | - (void)setupUI { 28 | switch (self.model.type) { 29 | case CLAssetMediaTypeImage: 30 | [self loadNormalImage]; 31 | break; 32 | 33 | case CLAssetMediaTypeGif: 34 | CLAllowSelectGif ? [self loadGifImage] : [self loadNormalImage]; 35 | break; 36 | 37 | case CLAssetMediaTypeLivePhoto: 38 | CLAllowSelectLivePhoto ? [self loadLivePhoto] : [self loadNormalImage]; 39 | break; 40 | 41 | case CLAssetMediaTypeVideo: 42 | [self loadVideo]; 43 | break; 44 | 45 | default: 46 | break; 47 | } 48 | } 49 | 50 | #pragma mark - 加载静态图 51 | - (void)loadNormalImage { 52 | UIImageView *imageView = [[UIImageView alloc] init]; 53 | imageView.contentMode = UIViewContentModeScaleAspectFit; 54 | imageView.frame = (CGRect){CGPointZero, self.preferredContentSize}; 55 | [self.view addSubview:imageView]; 56 | [CLPhotoShareManager requestCustomImageForAsset:self.model.asset size:CGSizeMake(self.preferredContentSize.width*2, self.preferredContentSize.height*2) completion:^(UIImage *image, NSDictionary *info) { 57 | imageView.image = image; 58 | }]; 59 | } 60 | 61 | - (void)loadGifImage { 62 | UIImageView *imageView = [[UIImageView alloc] init]; 63 | imageView.contentMode = UIViewContentModeScaleAspectFit; 64 | imageView.frame = (CGRect){CGPointZero, self.preferredContentSize}; 65 | [self.view addSubview:imageView]; 66 | [CLPhotoShareManager requestOriginalImageDataForAsset:self.model.asset completion:^(NSData *data, NSDictionary *info) { 67 | imageView.image = [CLPhotoManager transformToGifImageWithData:data]; 68 | }]; 69 | } 70 | 71 | - (void)loadLivePhoto { 72 | if (@available(iOS 9.1, *)) { 73 | PHLivePhotoView *lpView = [[PHLivePhotoView alloc] init]; 74 | lpView.contentMode = UIViewContentModeScaleAspectFit; 75 | lpView.muted = NO; 76 | lpView.frame = (CGRect){CGPointZero, self.preferredContentSize}; 77 | [self.view addSubview:lpView]; 78 | cl_weakSelf(self); 79 | [CLPhotoShareManager requestLivePhotoForAsset:self.model.asset completion:^(PHLivePhoto *livePhoto, NSDictionary *info) { 80 | cl_strongSelf(weakSelf); 81 | if (!strongSelf) { 82 | return; 83 | } 84 | lpView.livePhoto = livePhoto; 85 | [lpView startPlaybackWithStyle:PHLivePhotoViewPlaybackStyleFull]; 86 | }]; 87 | } else { 88 | // Fallback on earlier versions 89 | } 90 | } 91 | 92 | - (void)loadVideo { 93 | AVPlayerLayer *playLayer = [[AVPlayerLayer alloc] init]; 94 | playLayer.frame = (CGRect){CGPointZero, self.preferredContentSize}; 95 | [self.view.layer addSublayer:playLayer]; 96 | cl_weakSelf(self); 97 | [CLPhotoShareManager requestVideoPlayerItemForAsset:self.model.asset completion:^(AVPlayerItem *item, NSDictionary *info) { 98 | cl_strongSelf(weakSelf); 99 | if (!strongSelf) { 100 | return; 101 | } 102 | dispatch_async(dispatch_get_main_queue(), ^{ 103 | AVPlayer *player = [AVPlayer playerWithPlayerItem:item]; 104 | playLayer.player = player; 105 | [player play]; 106 | }); 107 | }]; 108 | } 109 | 110 | - (void)didReceiveMemoryWarning { 111 | [super didReceiveMemoryWarning]; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /Example/CLPhotoLib.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/CLPhotoLib.xcodeproj/xcshareddata/xcschemes/CLPhotoLib-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Example/CLPhotoLib.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/CLPhotoLib.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/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 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/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 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/CLAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLAppDelegate.h 3 | // CLPhotoLib 4 | // 5 | // Created by claudeli@yeah.net on 10/15/2018. 6 | // Copyright (c) 2018 claudeli@yeah.net. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface CLAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/CLAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLAppDelegate.m 3 | // CLPhotoLib 4 | // 5 | // Created by claudeli@yeah.net on 10/15/2018. 6 | // Copyright (c) 2018 claudeli@yeah.net. All rights reserved. 7 | // 8 | 9 | #import "CLAppDelegate.h" 10 | 11 | @implementation CLAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/CLPhotoLib-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | 请允许访问你的摄像头 7 | NSMicrophoneUsageDescription 8 | 请允许访问你的麦克风 9 | NSPhotoLibraryAddUsageDescription 10 | 请允许访问你的相册 11 | NSPhotoLibraryUsageDescription 12 | 请允许访问你的相册 13 | CFBundleDevelopmentRegion 14 | en 15 | CFBundleDisplayName 16 | ${PRODUCT_NAME} 17 | CFBundleExecutable 18 | ${EXECUTABLE_NAME} 19 | CFBundleIdentifier 20 | $(PRODUCT_BUNDLE_IDENTIFIER) 21 | CFBundleInfoDictionaryVersion 22 | 6.0 23 | CFBundleName 24 | ${PRODUCT_NAME} 25 | CFBundlePackageType 26 | APPL 27 | CFBundleShortVersionString 28 | 1.0 29 | CFBundleSignature 30 | ???? 31 | CFBundleVersion 32 | 1.0 33 | LSRequiresIPhoneOS 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UIRequiredDeviceCapabilities 40 | 41 | armv7 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UISupportedInterfaceOrientations~ipad 50 | 51 | UIInterfaceOrientationPortrait 52 | UIInterfaceOrientationPortraitUpsideDown 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/CLPhotoLib-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | @import UIKit; 15 | @import Foundation; 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/CLViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLViewController.h 3 | // CLPhotoLib 4 | // 5 | // Created by claudeli@yeah.net on 10/15/2018. 6 | // Copyright (c) 2018 claudeli@yeah.net. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface CLViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/CLViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLViewController.m 3 | // CLPhotoLib 4 | // 5 | // Created by claudeli@yeah.net on 10/15/2018. 6 | // Copyright (c) 2018 claudeli@yeah.net. All rights reserved. 7 | // 8 | 9 | #import "CLViewController.h" 10 | #import 11 | 12 | @interface CLViewController () 13 | 14 | @end 15 | 16 | @implementation CLViewController 17 | 18 | - (void)viewDidLoad 19 | { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view, typically from a nib. 22 | } 23 | 24 | - (IBAction)clickPicker:(id)sender { 25 | CLPickerRootController *picker = [[CLPickerRootController alloc] init]; 26 | picker.pickerDelegate = self; 27 | picker.allowAutorotate = YES; 28 | picker.allowPanGestureSelect = YES; 29 | picker.minDuration = 3.0f; 30 | picker.maxDuration = 60.0f; 31 | [self presentViewController:picker animated:YES completion:nil]; 32 | } 33 | 34 | // images 35 | - (void)clPickerController:(CLPickerRootController *)picker didFinishPickingPhotos:(NSArray *)photos assets:(NSArray *)assets{ 36 | NSLog(@"========== %@", photos); 37 | } 38 | 39 | // video 40 | - (void)clPickerController:(CLPickerRootController *)picker didFinishPickingVideoCover:(UIImage *)videoCover videoURL:(NSURL *)videoURL{ 41 | NSLog(@"========== %@", videoURL); 42 | } 43 | 44 | // Cancel Picker 45 | - (void)clPickerControllerDidCancel:(CLPickerRootController *)picker{ 46 | NSLog(@"========== Cancel =========="); 47 | } 48 | 49 | // User can write Custom video camera. 50 | - (void)clPickerControllerDidShootVideo:(CLPickerRootController *)picker{ 51 | NSLog(@"========== ShootVideo =========="); 52 | } 53 | 54 | 55 | - (void)didReceiveMemoryWarning 56 | { 57 | [super didReceiveMemoryWarning]; 58 | // Dispose of any resources that can be recreated. 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/Images.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" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/CLPhotoLib/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CLPhotoLib 4 | // 5 | // Created by claudeli@yeah.net on 10/15/2018. 6 | // Copyright (c) 2018 claudeli@yeah.net. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "CLAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([CLAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'CLPhotoLib_Example' do 4 | pod 'CLPhotoLib', :path => '../' 5 | 6 | target 'CLPhotoLib_Tests' do 7 | inherit! :search_paths 8 | 9 | 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CLPhotoLib (0.1.0): 3 | - CLProgressFPD 4 | - CLProgressFPD (1.0.0) 5 | 6 | DEPENDENCIES: 7 | - CLPhotoLib (from `../`) 8 | 9 | SPEC REPOS: 10 | https://github.com/cocoapods/specs.git: 11 | - CLProgressFPD 12 | 13 | EXTERNAL SOURCES: 14 | CLPhotoLib: 15 | :path: "../" 16 | 17 | SPEC CHECKSUMS: 18 | CLPhotoLib: 324b8987c913158d490bcecf3d15cad374f28094 19 | CLProgressFPD: 903f02cdc88f513fdb0de1cc0c9e824d6a678e54 20 | 21 | PODFILE CHECKSUM: 34d19d87f4320d03b51967ca56c90a13b0be14f5 22 | 23 | COCOAPODS: 1.5.3 24 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Example/Tests/Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoLibTests.m 3 | // CLPhotoLibTests 4 | // 5 | // Created by claudeli@yeah.net on 10/15/2018. 6 | // Copyright (c) 2018 claudeli@yeah.net. All rights reserved. 7 | // 8 | 9 | @import XCTest; 10 | 11 | @interface Tests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation Tests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | 36 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 claudeli@yeah.net 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CLPhotoLib 2 | 3 | [![CI Status](https://img.shields.io/travis/claudeli@yeah.net/CLPhotoLib.svg?style=flat)](https://travis-ci.org/claudeli@yeah.net/CLPhotoLib) 4 | [![Version](https://img.shields.io/cocoapods/v/CLPhotoLib.svg?style=flat)](https://cocoapods.org/pods/CLPhotoLib) 5 | [![License](https://img.shields.io/cocoapods/l/CLPhotoLib.svg?style=flat)](https://cocoapods.org/pods/CLPhotoLib) 6 | [![Platform](https://img.shields.io/cocoapods/p/CLPhotoLib.svg?style=flat)](https://cocoapods.org/pods/CLPhotoLib) 7 | 8 | ## Example 9 | 10 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 11 | 12 | ## Requirements 13 | 14 | ``` 15 | a.视频、图片(包含GIF、LivePhoto、原图)的选择 16 | b.3Dtouch预览 17 | c.手势滑动选择 18 | d.视频压缩,裁剪,水印及自定义颜色填充处理 19 | e.图片处理(待处理) 20 | f.支持多语言 21 | g.适配iOS 11,iPhone X 22 | ``` 23 | 24 | ## Installation 25 | 26 | CLPhotoLib is available through [CocoaPods](https://cocoapods.org). To install 27 | it, simply add the following line to your Podfile: 28 | 29 | ```ruby 30 | pod 'CLPhotoLib' 31 | ``` 32 | 33 | ``` 34 | Plist Notes: 35 | CFBundleAllowMixedLocalizations 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSCameraUsageDescription 40 | 请允许访问摄像头 41 | NSMicrophoneUsageDescription 42 | 请允许访问麦克风 43 | NSPhotoLibraryAddUsageDescription 44 | 请允许访问相册 45 | NSPhotoLibraryUsageDescription 46 | 请允许访问相册 47 | ``` 48 | 49 | ## Author 50 | 51 | claudeli@yeah.net 52 | 53 | ## License 54 | 55 | CLPhotoLib is available under the MIT license. See the LICENSE file for more info. 56 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------