├── .DS_Store ├── .gitignore ├── .travis.yml ├── AssetsPickerViewController.podspec ├── AssetsPickerViewController ├── .DS_Store ├── Assets │ ├── .gitkeep │ ├── ar.lproj │ │ └── AssetsPickerViewController.strings │ ├── de.lproj │ │ └── AssetsPickerViewController.strings │ ├── en.lproj │ │ └── AssetsPickerViewController.strings │ ├── es-MX.lproj │ │ └── AssetsPickerViewController.strings │ ├── es.lproj │ │ └── AssetsPickerViewController.strings │ ├── fr.lproj │ │ └── AssetsPickerViewController.strings │ ├── hi-IN.lproj │ │ └── AssetsPickerViewController.strings │ ├── id.lproj │ │ └── AssetsPickerViewController.strings │ ├── ja.lproj │ │ └── AssetsPickerViewController.strings │ ├── ko.lproj │ │ └── AssetsPickerViewController.strings │ ├── ru.lproj │ │ └── AssetsPickerViewController.strings │ ├── tr.lproj │ │ └── AssetsPickerViewController.strings │ ├── zh-Hans.lproj │ │ └── AssetsPickerViewController.strings │ └── zh-Hant.lproj │ │ └── AssetsPickerViewController.strings └── Classes │ ├── .DS_Store │ ├── .gitkeep │ ├── Album │ ├── .DS_Store │ ├── Controller │ │ └── AssetsAlbumViewController.swift │ └── View │ │ ├── AssetsAlbumCell.swift │ │ ├── AssetsAlbumHeaderView.swift │ │ ├── AssetsAlbumLayout.swift │ │ └── AssetsAlbumTitleView.swift │ ├── Assets │ ├── AssetsManager+Sync.swift │ ├── AssetsManager.swift │ └── AssetsUtility.swift │ ├── Common │ ├── AssetsFetchService.swift │ └── SwiftAlert.swift │ ├── Crop │ ├── .DS_Store │ └── Controller │ │ └── AssetsCropViewController.swift │ ├── Library │ ├── Extensions │ │ ├── UIColor+ARGB.swift │ │ ├── UIView+Dimmer.swift │ │ ├── UIView+Fade.swift │ │ └── UIView+KVO.swift │ └── TinyLog │ │ └── TinyLog.swift │ ├── Photo │ ├── .DS_Store │ ├── Controller │ │ ├── AssetsPhotoViewController+AssetsManager.swift │ │ ├── AssetsPhotoViewController+Collection.swift │ │ ├── AssetsPhotoViewController+Delegate.swift │ │ ├── AssetsPhotoViewController+Model.swift │ │ ├── AssetsPhotoViewController+Selection.swift │ │ ├── AssetsPhotoViewController+Setup.swift │ │ ├── AssetsPhotoViewController+UI.swift │ │ ├── AssetsPhotoViewController.swift │ │ └── AssetsPreviewController.swift │ └── View │ │ ├── AssetsEmptyView.swift │ │ ├── AssetsFooterView.swift │ │ ├── AssetsNoPermissionView.swift │ │ ├── AssetsPhotoCell.swift │ │ ├── AssetsPhotoCellOverlay.swift │ │ ├── AssetsPhotoLayout.swift │ │ └── PanoramaIconView.swift │ ├── Picker │ ├── .DS_Store │ ├── AssetsCameraManager.swift │ ├── AssetsPickerConfig.swift │ ├── AssetsPickerCustomStringConfig.swift │ ├── Controller │ │ └── AssetsPickerViewController.swift │ ├── LogConfig.swift │ └── View │ │ ├── AssetsGuideView.swift │ │ └── SSCheckMark.swift │ └── Utility │ ├── Bundle+AssetsPickerViewController.swift │ ├── NumberFormatter+AssetsPickerViewController.swift │ ├── String+AssetsPickerViewController.swift │ ├── UIColor+AssetsPickerViewController.swift │ ├── UIFont+AssetsPickerViewController.swift │ └── UIScreen+AssetsPickerViewController.swift ├── Common ├── AssetsFetchService.swift └── SwiftAlert.swift ├── Example ├── AssetsPickerViewController.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── AssetsPickerViewController-Example.xcscheme ├── AssetsPickerViewController.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── AssetsPickerViewController │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Common │ │ └── CommonExampleController.swift │ ├── ExampleUsages │ │ ├── AlbumFilterBlockController.swift │ │ ├── AlbumFilterOptionController.swift │ │ ├── AlbumOrderBlockController.swift │ │ ├── AlbumOrderOptionController.swift │ │ ├── AssetFilterOptionController.swift │ │ ├── AssetOrderOptionController.swift │ │ ├── BasicUsageController.swift │ │ ├── CustomAlbumCellController.swift │ │ ├── CustomAssetCellController.swift │ │ ├── HideEmptyAlbumsController.swift │ │ ├── PreSelectedAssetsController.swift │ │ ├── ShowAlbumListOnStartupController.swift │ │ └── ShowHiddenAlbumController.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── MainViewController.swift │ ├── ar.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── de.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── es-MX.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── es.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── fr.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── id.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── it.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── ja.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── ko.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── ru.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── tr.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── zh-Hans.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ └── zh-Hant.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings ├── Podfile └── Podfile.lock ├── LICENSE ├── Package.swift ├── README.md └── _Pods.xcodeproj /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | Package.resolved 41 | .build/ 42 | .swiftpm/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots 69 | fastlane/test_output 70 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode10 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 -workspace Example/AssetsPickerViewController.xcworkspace -scheme AssetsPickerViewController-Example -sdk iphonesimulator12.0 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /AssetsPickerViewController.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint AssetsPickerViewController.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 http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'AssetsPickerViewController' 11 | s.version = '2.9.7' 12 | s.summary = 'Picker controller that supports multiple photos and videos written in Swift.' 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 | Select multiple photos and videos. 22 | Fully customizable UI. 23 | DESC 24 | 25 | s.homepage = 'https://github.com/DragonCherry/AssetsPickerViewController' 26 | s.screenshots = 'https://cloud.githubusercontent.com/assets/20486591/26525538/42b1d6dc-4395-11e7-9c16-b9abdb2e9247.PNG', 'https://cloud.githubusercontent.com/assets/20486591/26616648/1d385746-460b-11e7-9324-62ea634e2fcb.png' 27 | s.license = { :type => 'MIT', :file => 'LICENSE' } 28 | s.author = { 'DragonCherry' => 'dragoncherry@naver.com' } 29 | s.source = { :git => 'https://github.com/DragonCherry/AssetsPickerViewController.git', :tag => s.version.to_s } 30 | s.social_media_url = 'https://www.linkedin.com/in/jeongyong' 31 | 32 | s.ios.deployment_target = '10.0' 33 | 34 | s.source_files = 'AssetsPickerViewController/Classes/**/*' 35 | 36 | s.resource_bundles = { 37 | 'AssetsPickerViewController' => ['AssetsPickerViewController/Assets/*.*'] 38 | } 39 | s.swift_version = '5.1' 40 | 41 | # s.public_header_files = 'Pod/Classes/**/*.h' 42 | # s.frameworks = 'UIKit', 'MapKit' 43 | s.dependency 'SnapKit' 44 | end 45 | -------------------------------------------------------------------------------- /AssetsPickerViewController/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/AssetsPickerViewController/.DS_Store -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/AssetsPickerViewController/Assets/.gitkeep -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/ar.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "إلغاء"; 4 | "Done" = "تم"; 5 | "Photo" = "صور"; 6 | "Video" = "فيديو"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "ألبومات"; 10 | "Title_Section_MyAlbums" = "ألبوماتي"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ صور"; 14 | "Footer_Videos" = "%@ فيديوهات"; 15 | "Footer_Items" = "%@ صور، %@ فيديو"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "تم تحديد %@ صورة"; 19 | "Title_Selected_Photos" = "تم تحديد %@ صور"; 20 | "Title_Selected_Video" = "تم تحديد %@ فيديو"; 21 | "Title_Selected_Videos" = "تم تحديد %@ فيديوهات"; 22 | "Title_Selected_Items" = "تم تحديد %@ عناصر"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "لا توجد صور أو فيديوهات"; 26 | "Message_No_Items" = "يمكنك التقاط الصور والفيديوهات باستخدام الكاميرا، أو مزامنة الصورة والفيديوهات مع %@ الخاص بك.\nباستخدام iTunes."; 27 | "Message_No_Items_Camera" = "يمكنك مزامنة الصورة والفيديوهات مع %@ الخاص بك."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "لا يحق لهذا التطبيق الوصول إلى صورك أو فيديوهاتك."; 31 | "Message_No_Permission" = "يمكن تفعيل الوصول من إعدادات الخصوصية."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/de.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "Abbrechen"; 4 | "Done" = "Fertig"; 5 | "Photo" = "Foto"; 6 | "Video" = "Video"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "Alben"; 10 | "Title_Section_MyAlbums" = "Meine Alben"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ Fotos"; 14 | "Footer_Videos" = "%@ Videos"; 15 | "Footer_Items" = "%@ Fotos, %@ Videos"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ Foto ausgewählt"; 19 | "Title_Selected_Photos" = "%@ Fotos ausgewählt"; 20 | "Title_Selected_Video" = "%@ Video ausgewählt"; 21 | "Title_Selected_Videos" = "%@ Videos ausgewählt"; 22 | "Title_Selected_Items" = "%@ Objekte ausgewählt"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "Keine Fotos oder Videos vorhanden"; 26 | "Message_No_Items" = "Sie können Fotos oder Videos mit der Kamera aufnehmen oder Ihr %@ mit iTunes synchronisieren."; 27 | "Message_No_Items_Camera" = "Sie können Fotos und Videos mit iTunes auf Ihr %@ synchronisieren."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "Diese App darf nicht auf Ihr Fotoalbum zugreifen."; 31 | "Message_No_Permission" = "Sie können den Zugriff in den Datenschutz Einstellungen erlauben."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/en.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "Cancel"; 4 | "Done" = "Done"; 5 | "Photo" = "Photo"; 6 | "Video" = "Video"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "Albums"; 10 | "Title_Section_MyAlbums" = "My Albums"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ Photos"; 14 | "Footer_Videos" = "%@ Videos"; 15 | "Footer_Items" = "%@ Photos, %@ Videos"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ Photo Selected"; 19 | "Title_Selected_Photos" = "%@ Photos Selected"; 20 | "Title_Selected_Video" = "%@ Video Selected"; 21 | "Title_Selected_Videos" = "%@ Videos Selected"; 22 | "Title_Selected_Items" = "%@ Items Selected"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "No Photos or Videos"; 26 | "Message_No_Items" = "You can take photos and videos using the camera, or sync photos and videos onto your %@ using iTunes."; 27 | "Message_No_Items_Camera" = "You can sync photos and videos onto your %@ using iTunes."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "This app does not have access to your photos or videos."; 31 | "Message_No_Permission" = "You can enable access in Privacy Settings."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/es-MX.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "Cancela"; 4 | "Done" = "OK"; 5 | "Photo" = "Foto"; 6 | "Video" = "Video"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "Albums"; 10 | "Title_Section_MyAlbums" = "My Albums"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ Fotos"; 14 | "Footer_Videos" = "%@ Videos"; 15 | "Footer_Items" = "%@ Fotos, %@ Videos"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ Foto seleccionada"; 19 | "Title_Selected_Photos" = "%@ Fotos seleccionadas"; 20 | "Title_Selected_Video" = "%@ Video seleccionado"; 21 | "Title_Selected_Videos" = "%@ Videos seleccionados"; 22 | "Title_Selected_Items" = "%@ Objetos seleccionados"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "Ni fotos ni videos"; 26 | "Message_No_Items" = "Puedes tomar fotos y videos utilizando la cámara, o sincronizarlas con tu %@ con iTunes."; 27 | "Message_No_Items_Camera" = "Puedes sincronizar las fotos y videos en tu %@ con iTunes"; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "La aplicación no tiene acceso a tus fotos o videos."; 31 | "Message_No_Permission" = "Puedes acceder en Ajustes/Privacidad."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/es.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "Cancelar"; 4 | "Done" = "Hecho"; 5 | "Photo" = "foto"; 6 | "Video" = "vídeo"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "Álbumes"; 10 | "Title_Section_MyAlbums" = "Mis álbumes"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ fotos"; 14 | "Footer_Videos" = "%@ vídeos"; 15 | "Footer_Items" = "%@ fotos, %@ vídeos"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ foto seleccionada"; 19 | "Title_Selected_Photos" = "%@ fotos seleccionadas"; 20 | "Title_Selected_Video" = "%@ vídeo seleccionado"; 21 | "Title_Selected_Videos" = "%@ vídeos seleccionados"; 22 | "Title_Selected_Items" = "%@ objetos seleccionados"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "No hay fotos ni vídeos"; 26 | "Message_No_Items" = "Puedes tomar fotos y vídeos con la cámara o sincronizar fotos y vídeos en tu %@ a través de iTunes."; 27 | "Message_No_Items_Camera" = "Puedes sincronizar fotos y vídeos en tu %@ a través de iTunes."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "Esta aplicación no tiene acceso a tus fotos o vídeos."; 31 | "Message_No_Permission" = "Puedes habilitar el acceso en los ajustes de privacidad."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/fr.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "Annuler"; 4 | "Done" = "Valider"; 5 | "Photo" = "photo"; 6 | "Video" = "vidéo"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "Albums"; 10 | "Title_Section_MyAlbums" = "Mes albums"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ Photos"; 14 | "Footer_Videos" = "%@ Vidéos"; 15 | "Footer_Items" = "%@ Photos, %@ Vidéos"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ photo selectionée"; 19 | "Title_Selected_Photos" = "%@ photos sélectionnées"; 20 | "Title_Selected_Video" = "%@ vidéo sélectionnée"; 21 | "Title_Selected_Videos" = "%@ vidéos sélectionnées"; 22 | "Title_Selected_Items" = "%@ éléments sélectionnés"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "Pas de Photos ou Vidéos"; 26 | "Message_No_Items" = "Vous pouvez prendre des photos et des vidéos en utilisant l'appareil ou synchroniser vos photos et vidéos sur %@ avec iTunes."; 27 | "Message_No_Items_Camera" = "Vous pouvez synchroniser vos photos et vidéos sur %@ avec iTunes."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "Cette application n'a pas accès à vos photos et vos vidéos."; 31 | "Message_No_Permission" = "Vous pouvez autoriser l'accès dans l'application Réglages > Confidentialité."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/hi-IN.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "रद्द करें"; 4 | "Done" = "पूर्ण हुआ"; 5 | "Photo" = "चित्र"; 6 | "Video" = "वीडियो"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "एल्बम"; 10 | "Title_Section_MyAlbums" = "मेरे एलबम"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ चित्र"; 14 | "Footer_Videos" = "%@ वीडियो"; 15 | "Footer_Items" = "%@ चित्र, %@ वीडियो"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ चित्र चयनित"; 19 | "Title_Selected_Photos" = "%@ चित्र चयनित"; 20 | "Title_Selected_Video" = "%@ वीडियो चयनित"; 21 | "Title_Selected_Videos" = "%@ वीडियो चयनित"; 22 | "Title_Selected_Items" = "%@ आइटम चयनित"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "कोई चित्र या वीडियो नहीं"; 26 | "Message_No_Items" = "आप कैमरा का उपयोग करके चित्र या वीडियो ले सकते हैं, अथवा iTunes का उपयोग करके चित्र या वीडियो अपने %@ पर सिंक कर सकते हैं।"; 27 | "Message_No_Items_Camera" = "आप iTunes का उपयोग करके अपने %@ पर चित्र और वीडियो सिंक कर सकते हैं।"; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "यह ऐप आपके चित्रों या वीडियो तक नहीं पहुंच सकता।"; 31 | "Message_No_Permission" = "आप निजता सेटिंग्स में पहुंच सक्षम कर सकते हैं।"; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/id.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "Batal"; 4 | "Done" = "Selesai"; 5 | "Photo" = "Foto"; 6 | "Video" = "Video"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "Albums"; 10 | "Title_Section_MyAlbums" = "Album saya"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ Foto"; 14 | "Footer_Videos" = "%@ Video"; 15 | "Footer_Items" = "%@ Foto, %@ Video"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ Foto Terpilih"; 19 | "Title_Selected_Photos" = "%@ Foto Terpilih"; 20 | "Title_Selected_Video" = "%@ Video Terpilih"; 21 | "Title_Selected_Videos" = "%@ Video Terpilih"; 22 | "Title_Selected_Items" = "%@ Item Terpilih"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "Tidak ada Foto atau Video"; 26 | "Message_No_Items" = "Kamu bisa mengambil foto dan video menggunakan kamera, atau menyinkronkan foto dan video ke %@ menggunakan iTunes."; 27 | "Message_No_Items_Camera" = "Kamu bisa menyinkronkan foto dan video ke %@ menggunakan iTunes."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "Aplikasi ini tidak memiliki akses ke foto atau videomu."; 31 | "Message_No_Permission" = "Kamu bisa mengaktifkan akses di Setelan Privasi."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/ja.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "キャンセル"; 4 | "Done" = "完了"; 5 | "Photo" = "写真"; 6 | "Video" = "動画"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "アルバム"; 10 | "Title_Section_MyAlbums" = "私のアルバム"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ 枚の写真"; 14 | "Footer_Videos" = "%@ 件の動画"; 15 | "Footer_Items" = "%@ 枚の写真、%@ 件の動画"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ 枚の写真が選択されました"; 19 | "Title_Selected_Photos" = "%@ 枚の写真が選択されました"; 20 | "Title_Selected_Video" = "%@ 件の動画が選択されました"; 21 | "Title_Selected_Videos" = "%@ 件の動画が選択されました"; 22 | "Title_Selected_Items" = "%@ 個のアイテムが選択されました"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "写真または動画がありません"; 26 | "Message_No_Items" = "カメラを使用して写真または動画を撮影するか、ご利用の%@へ写真と動画を同期することができます。同期はiTunes を使用して行えます。"; 27 | "Message_No_Items_Camera" = "iTunes を使用して、ご利用の%@へ写真と動画を同期することができます。"; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "このアプリはあなたの写真と動画へアクセスする許可を得ていません。"; 31 | "Message_No_Permission" = "「プライバシー設定」からアクセスを許可できます。"; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/ko.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "취소"; 4 | "Done" = "완료"; 5 | "Photo" = "사진"; 6 | "Video" = "비디오"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "앨범"; 10 | "Title_Section_MyAlbums" = "나의 앨범"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "사진 %@개"; 14 | "Footer_Videos" = "비디오 %@개"; 15 | "Footer_Items" = "%@장의 사진, %@개의 비디오"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@장의 사진이 선택됨"; 19 | "Title_Selected_Photos" = "%@장의 사진이 선택됨"; 20 | "Title_Selected_Video" = "%@개의 비디오가 선택됨"; 21 | "Title_Selected_Videos" = "%@개의 비디오가 선택됨"; 22 | "Title_Selected_Items" = "%@개의 항목 선택됨"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "사진 또는 비디오 없음"; 26 | "Message_No_Items" = "카메라를 통해 사진 및 비디오를 찍거나 iTunes를 통해 %@에서 사진 및 비디오를 iPhone에 동기화할 수 있습니다."; 27 | "Message_No_Items_Camera" = "iTunes를 통해 %@에서 사진 및 비디오를 동기화할 수 있습니다."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "이 앱은 귀하의 사진 또는 비디오에 접근 권한이 없습니다."; 31 | "Message_No_Permission" = "개인정보 설정에서 접근을 활성화할 수 있습니다."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/ru.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "Отмена"; 4 | "Done" = "Готово"; 5 | "Photo" = "фотография"; 6 | "Video" = "видео"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "Альбомы"; 10 | "Title_Section_MyAlbums" = "Мои альбомы"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ фотографий"; 14 | "Footer_Videos" = "%@ видео"; 15 | "Footer_Items" = "%@ фотографий, %@ видео"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "выбрана %@ фотография"; 19 | "Title_Selected_Photos" = "выбрано %@ фотографий"; 20 | "Title_Selected_Video" = "выбрано %@ видео"; 21 | "Title_Selected_Videos" = "выбрано %@ видео"; 22 | "Title_Selected_Items" = "выбрано %@ элементов"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "Нет фотографий и видео"; 26 | "Message_No_Items" = "Вы можете сделать фотографии или снять видео при помощи камеры либо синхронизировать фотографии и видео со своим %@ через iTunes."; 27 | "Message_No_Items_Camera" = "Вы можете синхронизировать фотографии и видео со своим %@ при помощи iTunes."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "У этого приложения нет доступа к вашим фотографиям и видео."; 31 | "Message_No_Permission" = "Вы можете включить доступ приложению в Настройках конфиденциальности."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/tr.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "Vazgeç"; 4 | "Done" = "Bitti"; 5 | "Photo" = "Fotoğraf"; 6 | "Video" = "Vidyo"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "Albümler"; 10 | "Title_Section_MyAlbums" = "Albümlerim"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@ Fotoğraf"; 14 | "Footer_Videos" = "%@ Vidyo"; 15 | "Footer_Items" = "%@ Fotoğraf, %@ Vidyo"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "%@ Fotoğraf seçildi"; 19 | "Title_Selected_Photos" = "%@ Fotoğraf seçildi"; 20 | "Title_Selected_Video" = "%@ Vidyo seçildi"; 21 | "Title_Selected_Videos" = "%@ Vidyo seçildi"; 22 | "Title_Selected_Items" = "%@ Öğe seçildi"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "Hiç fotoğraf ya da vidyo bulunamadı"; 26 | "Message_No_Items" = "Kamerayı kullanarak fotoğraf ve vidyo çekebilirsiniz ya da fotoğraf ve vidyolarınızı %@ hesabınıza iTunes kullanarak eşleyebilirsiniz."; 27 | "Message_No_Items_Camera" = "Fotoğraf ve vidyoları iTunes kullanarak %@ hesabınıza eşleyebilirsiniz."; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "Bu uygulamanın fotoğraflarınıza ya da vidyolarınıza erişim hakkı bulunmuyor."; 31 | "Message_No_Permission" = "Gizlilik Ayarlarından uygulamaya izin verebilirsiniz."; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/zh-Hans.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "取消"; 4 | "Done" = "完成"; 5 | "Photo" = "照片"; 6 | "Video" = "视频"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "相册"; 10 | "Title_Section_MyAlbums" = "我的相册"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@张照片"; 14 | "Footer_Videos" = "%@个视频"; 15 | "Footer_Items" = "%@张照片,%@个视频"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "已选择%@张照片"; 19 | "Title_Selected_Photos" = "已选择%@张照片"; 20 | "Title_Selected_Video" = "已选择%@个视频"; 21 | "Title_Selected_Videos" = "已选择%@个视频"; 22 | "Title_Selected_Items" = "已选择%@项"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "无照片或视频"; 26 | "Message_No_Items" = "您可以使用摄像头拍摄照片和视频,或使用iTunes将照片和视频同步到您的%@上。"; 27 | "Message_No_Items_Camera" = "您可以使用iTunes将照片和视频同步到您的%@上。"; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "此应用程序对您的照片或视频没有访问权。"; 31 | "Message_No_Permission" = "您可以在隐私设置中启用访问权。"; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Assets/zh-Hant.lproj/AssetsPickerViewController.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Common */ 3 | "Cancel" = "取消"; 4 | "Done" = "完成"; 5 | "Photo" = "照片"; 6 | "Video" = "影片"; 7 | 8 | /* Albums */ 9 | "Title_Albums" = "相册"; 10 | "Title_Section_MyAlbums" = "我的相册"; 11 | 12 | /* Footer */ 13 | "Footer_Photos" = "%@张照片"; 14 | "Footer_Videos" = "%@个视频"; 15 | "Footer_Items" = "%@张照片,%@个视频"; 16 | 17 | /* Selected Status on Title */ 18 | "Title_Selected_Photo" = "已選取 %@ 張照片"; 19 | "Title_Selected_Photos" = "已選取 %@ 張照片"; 20 | "Title_Selected_Video" = "已選取 %@ 部影片"; 21 | "Title_Selected_Videos" = "已選取 %@ 部影片"; 22 | "Title_Selected_Items" = "已選取 %@ 個項目"; 23 | 24 | /* No Items in Photo Library */ 25 | "Title_No_Items" = "沒有照片或影片"; 26 | "Message_No_Items" = "您可以用相機拍照錄影或用 iTunes 將照片和影片同步到 %@。"; 27 | "Message_No_Items_Camera" = "您可以使用 iTunes 將照片和影片同步到 %@。"; 28 | 29 | /* No Permission */ 30 | "Title_No_Permission" = "此 App 無法取用您的照片或影片。"; 31 | "Message_No_Permission" = "您可以在「隱私權設定」中啟用存取。"; 32 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/AssetsPickerViewController/Classes/.DS_Store -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/AssetsPickerViewController/Classes/.gitkeep -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Album/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/AssetsPickerViewController/Classes/Album/.DS_Store -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Album/View/AssetsAlbumCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsAlbumCell.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/17/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | public protocol AssetsAlbumCellProtocol { 13 | var album: PHAssetCollection? { get set } 14 | var isSelected: Bool { get set } 15 | var imageView: UIImageView { get } 16 | var titleText: String? { get set } 17 | var count: Int { get set } 18 | } 19 | 20 | open class AssetsAlbumCell: UICollectionViewCell, AssetsAlbumCellProtocol { 21 | 22 | // MARK: - AssetsAlbumCellProtocol 23 | open var album: PHAssetCollection? { 24 | didSet { 25 | // customizable 26 | } 27 | } 28 | 29 | public let imageView: UIImageView = { 30 | let view = UIImageView() 31 | view.backgroundColor = .ap_cellBackground 32 | view.contentMode = .scaleAspectFill 33 | view.clipsToBounds = true 34 | view.layer.cornerRadius = 5 35 | return view 36 | }() 37 | 38 | open var titleText: String? { 39 | didSet { 40 | titleLabel.text = titleText 41 | } 42 | } 43 | 44 | open var count: Int = 0 { 45 | didSet { 46 | countLabel.text = "\(NumberFormatter.decimalString(value: count))" 47 | } 48 | } 49 | 50 | // MARK: - Views 51 | fileprivate let titleLabel: UILabel = { 52 | let label = UILabel() 53 | label.textColor = .ap_label 54 | label.font = UIFont.systemFont(forStyle: .subheadline) 55 | return label 56 | }() 57 | 58 | fileprivate let countLabel: UILabel = { 59 | let label = UILabel() 60 | label.textColor = .ap_secondaryLabel 61 | label.font = UIFont.systemFont(forStyle: .subheadline) 62 | return label 63 | }() 64 | 65 | 66 | // MARK: - Lifecycle 67 | public required init?(coder aDecoder: NSCoder) { 68 | super.init(coder: aDecoder) 69 | commonInit() 70 | } 71 | 72 | public override init(frame: CGRect) { 73 | super.init(frame: frame) 74 | commonInit() 75 | } 76 | 77 | private func commonInit() { 78 | 79 | contentView.addSubview(imageView) 80 | contentView.addSubview(titleLabel) 81 | contentView.addSubview(countLabel) 82 | 83 | imageView.snp.makeConstraints { (make) in 84 | make.height.equalTo(imageView.snp.width) 85 | make.top.equalToSuperview() 86 | make.leading.equalToSuperview() 87 | make.trailing.equalToSuperview() 88 | } 89 | 90 | titleLabel.snp.makeConstraints { (make) in 91 | make.top.equalTo(imageView.snp.bottom).offset(8) 92 | make.leading.equalToSuperview() 93 | make.trailing.equalToSuperview() 94 | make.height.equalTo(titleLabel.font.pointSize + 2) 95 | } 96 | 97 | countLabel.snp.makeConstraints { (make) in 98 | make.top.equalTo(titleLabel.snp.bottom).offset(2) 99 | make.leading.equalToSuperview() 100 | make.trailing.equalToSuperview() 101 | make.height.equalTo(countLabel.font.pointSize + 2) 102 | //make.bottom.equalTo(snp.bottom).offset(8) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Album/View/AssetsAlbumHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsAlbumHeaderView.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/18/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | open class AssetsAlbumHeaderView: UICollectionReusableView { 12 | 13 | internal lazy var titleLabel: UILabel = { 14 | let label = UILabel() 15 | label.textColor = .ap_secondaryLabel 16 | label.font = UIFont.systemFont(forStyle: .title3) 17 | label.text = String(key: "Title_Section_MyAlbums") 18 | return label 19 | }() 20 | 21 | public required init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | commonInit() 24 | } 25 | 26 | public override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | commonInit() 29 | } 30 | 31 | private func commonInit() { 32 | addSubview(titleLabel) 33 | titleLabel.snp.makeConstraints { (make) in 34 | make.edges.equalToSuperview() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Album/View/AssetsAlbumLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsAlbumLayout.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/18/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | open class AssetsAlbumLayout: UICollectionViewFlowLayout { 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Album/View/AssetsAlbumTitleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsAlbumTitleView.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/19/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Assets/AssetsUtility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsUtility.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/19/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | 12 | open class AssetsUtility { 13 | 14 | public static func fetchOption(isIncludeImage: Bool = true, isIncludeVideo: Bool = true) -> PHFetchOptions { 15 | let options = PHFetchOptions() 16 | if isIncludeImage && isIncludeVideo { 17 | options.predicate = NSPredicate(format: "mediaType = %d OR mediaType = %d", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue) 18 | } else if isIncludeImage && !isIncludeVideo { 19 | options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue) 20 | } else if !isIncludeImage && isIncludeVideo { 21 | options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue) 22 | } 23 | return options 24 | } 25 | 26 | public static func sortedAssets(_ assets: [PHAsset], recentFirst: Bool = true) -> [PHAsset] { 27 | 28 | let sortedAssets = assets.sorted { (asset0, asset1) -> Bool in 29 | let order: ComparisonResult = recentFirst ? .orderedDescending : .orderedAscending 30 | 31 | var date0: Date? 32 | var date1: Date? 33 | 34 | if let modDate0 = asset0.modificationDate { 35 | date0 = modDate0 36 | } 37 | if let crDate0 = asset0.creationDate { 38 | if let modDate0 = asset0.modificationDate { 39 | if crDate0.compare(modDate0) == order { 40 | date0 = crDate0 41 | } else { 42 | date0 = modDate0 43 | } 44 | } 45 | } 46 | 47 | if let modDate1 = asset1.modificationDate { 48 | date1 = modDate1 49 | } 50 | if let crDate1 = asset1.creationDate { 51 | if let modDate1 = asset1.modificationDate { 52 | if crDate1.compare(modDate1) == order { 53 | date1 = crDate1 54 | } else { 55 | date1 = modDate1 56 | } 57 | } 58 | } 59 | 60 | if let date0 = date0, let date1 = date1 { 61 | return date0.compare(date1) == order 62 | } else { 63 | return false 64 | } 65 | } 66 | return sortedAssets 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Common/AssetsFetchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsFetchService.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/03. 6 | // 7 | 8 | import Photos 9 | 10 | // MARK: - Image Fetching IDs 11 | class AssetsFetchService { 12 | 13 | var requestMap = [IndexPath: PHImageRequestID]() 14 | 15 | func cancelFetching(at indexPath: IndexPath) { 16 | if let requestId = requestMap[indexPath] { 17 | requestMap.removeValue(forKey: indexPath) 18 | if LogConfig.isFetchLogEnabled { logd("Canceled ID: \(requestId) at: \(indexPath.row) (\(self.requestMap.count))") } 19 | AssetsManager.shared.cancelRequest(requestId: requestId) 20 | } 21 | } 22 | 23 | func registerFetching(requestId: PHImageRequestID, at indexPath: IndexPath) { 24 | requestMap[indexPath] = requestId 25 | if LogConfig.isFetchLogEnabled { logd("Requested ID: \(requestId) at: \(indexPath.row) (\(self.requestMap.count))") } 26 | } 27 | 28 | func removeFetching(indexPath: IndexPath) { 29 | if let requestId = requestMap[indexPath] { 30 | requestMap.removeValue(forKey: indexPath) 31 | if LogConfig.isFetchLogEnabled { logd("Finished ID: \(requestId) at: \(indexPath.row) (\(self.requestMap.count))") } 32 | } 33 | } 34 | 35 | func isFetching(indexPath: IndexPath) -> Bool { 36 | if let _ = requestMap[indexPath] { 37 | return true 38 | } else { 39 | return false 40 | } 41 | } 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Crop/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/AssetsPickerViewController/Classes/Crop/.DS_Store -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Crop/Controller/AssetsCropViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoCropViewController.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/17/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Library/Extensions/UIColor+ARGB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+ARGB.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 6/30/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | open var RGBString: String { 14 | let colorRef = cgColor.components 15 | let r: CGFloat = colorRef![0] 16 | let g: CGFloat = colorRef![1] 17 | let b: CGFloat = colorRef![2] 18 | return String(NSString(format: "%02lX%02lX%02lX", lroundf(Float(r * 255)), lroundf(Float(g * 255)), lroundf(Float(b * 255)))) 19 | } 20 | 21 | open var ARGBString: String { 22 | let colorRef = cgColor.components 23 | let a: CGFloat = cgColor.alpha 24 | let r: CGFloat = colorRef![0] 25 | let g: CGFloat = colorRef![1] 26 | let b: CGFloat = colorRef![2] 27 | return String(NSString(format: "%02lX%02lX%02lX%02lX", lroundf(Float(a * 255)), lroundf(Float(r * 255)), lroundf(Float(g * 255)), lroundf(Float(b * 255)))) 28 | } 29 | 30 | public convenience init(alpha: Float, red: Int, green: Int, blue: Int) { 31 | assert(red >= 0 && red <= 255, "Invalid red component") 32 | assert(green >= 0 && green <= 255, "Invalid green component") 33 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 34 | assert(alpha >= 0 || alpha <= 1, "Invalid alpha component") 35 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: CGFloat(alpha)) 36 | } 37 | 38 | public convenience init(red: Int, green: Int, blue: Int) { 39 | assert(red >= 0 && red <= 255, "Invalid red component") 40 | assert(green >= 0 && green <= 255, "Invalid green component") 41 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 42 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) 43 | } 44 | 45 | public convenience init(rgbHex: Int) { 46 | self.init( 47 | red: (rgbHex >> 16) & 0xff, 48 | green: (rgbHex >> 8) & 0xff, 49 | blue: rgbHex & 0xff) 50 | } 51 | 52 | convenience init(hexString: String) { 53 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 54 | var int = UInt32() 55 | Scanner(string: hex).scanHexInt32(&int) 56 | let a, r, g, b: UInt32 57 | switch hex.count { 58 | case 3: // RGB (12-bit) 59 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 60 | case 6: // RGB (24-bit) 61 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 62 | case 8: // ARGB (32-bit) 63 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 64 | default: 65 | (a, r, g, b) = (0, 0, 0, 0) 66 | } 67 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) 68 | } 69 | 70 | public convenience init(rgbHex: Int, alpha: Float) { 71 | self.init( 72 | red: CGFloat((rgbHex >> 16) & 0xff), 73 | green: CGFloat((rgbHex >> 8) & 0xff), 74 | blue: CGFloat(rgbHex & 0xff), 75 | alpha: CGFloat(alpha) / 255.0) 76 | } 77 | 78 | public convenience init(argbHex: UInt32) { 79 | let alpha: UInt32 = (argbHex >> 24) 80 | self.init( 81 | red: CGFloat((argbHex >> 16) & 0xff), 82 | green: CGFloat((argbHex >> 8) & 0xff), 83 | blue: CGFloat(argbHex & 0xff), 84 | alpha: CGFloat(alpha) / 255.0) 85 | } 86 | 87 | @objc static func image(from color: UIColor) -> UIImage? { 88 | let rect = CGRect(x: 0, y: 0, width: 1, height: 1) 89 | UIGraphicsBeginImageContext(rect.size) 90 | let context = UIGraphicsGetCurrentContext() 91 | context!.setFillColor(color.cgColor) 92 | context!.fill(rect) 93 | let img = UIGraphicsGetImageFromCurrentImageContext() 94 | UIGraphicsEndImageContext() 95 | return img 96 | } 97 | } 98 | 99 | extension UIColor { 100 | func image() -> UIImage? { 101 | 102 | var colorImage: UIImage? = nil 103 | 104 | UIGraphicsBeginImageContext(CGSize(width: 1, height: 1)) 105 | if let context = UIGraphicsGetCurrentContext() { 106 | context.setFillColor(self.cgColor) 107 | context.fill(CGRect(x: 0, y: 0, width: 1, height: 1)) 108 | 109 | colorImage = UIGraphicsGetImageFromCurrentImageContext() 110 | } 111 | UIGraphicsEndImageContext() 112 | 113 | return colorImage 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Library/Extensions/UIView+Fade.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Fade.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/12/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | 13 | public func fadeOutFromSuperview(_ duration: TimeInterval = 0.25, completion: (() -> Void)? = nil) { 14 | if let _ = self.superview { 15 | UIView.animate( 16 | withDuration: duration, 17 | animations: { 18 | self.alpha = 0 19 | }, 20 | completion: { finished in 21 | self.isHidden = true 22 | self.removeFromSuperview() 23 | completion?() 24 | }) 25 | } 26 | } 27 | 28 | public func fadeInSubview(_ subview: UIView, duration: TimeInterval = 0.25, completion: (() -> Void)? = nil) { 29 | if subview.superview == nil { 30 | addSubview(subview) 31 | subview.isHidden = false 32 | let originalAlpha = subview.alpha 33 | subview.alpha = 0 34 | UIView.animate( 35 | withDuration: duration, 36 | animations: { 37 | subview.alpha = originalAlpha 38 | }, 39 | completion: { finished in 40 | completion?() 41 | }) 42 | } 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Library/Extensions/UIView+KVO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+KVO.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 1/9/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | private let kUIViewKVODictionaryKey = "kUIViewKVODictionaryKey" 12 | 13 | public extension UIView { 14 | 15 | /** Set nil on first parameter to remove existing object for key. */ 16 | func set(_ object: Any?, forKey key: AnyHashable) { 17 | var dictionary: [AnyHashable: Any]! 18 | if let savedDictionary = self.layer.value(forKey: kUIViewKVODictionaryKey) as? [AnyHashable: Any] { 19 | dictionary = savedDictionary 20 | } else { 21 | dictionary = [AnyHashable: Any]() 22 | } 23 | if let object = object { 24 | dictionary[key] = object 25 | } else { 26 | dictionary.removeValue(forKey: key) 27 | } 28 | self.layer.setValue(dictionary, forKey: kUIViewKVODictionaryKey) 29 | } 30 | 31 | func get(_ key: AnyHashable) -> Any? { 32 | if let dictionary = self.layer.value(forKey: kUIViewKVODictionaryKey) as? [AnyHashable: Any] { 33 | return dictionary[key] 34 | } else { 35 | return nil 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Library/TinyLog/TinyLog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TinyLog.swift 3 | // TinyLog 4 | // 5 | // Created by DragonCherry on 1/11/17. 6 | // Copyright © 2017 DragonCherry. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class TinyLog { 12 | public static var stripParameters: Bool = true 13 | public static var isShowInfoLog = true 14 | public static var isShowErrorLog = true 15 | public static var filterString: String? 16 | 17 | static var isTestFlight: Bool { 18 | if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, appStoreReceiptURL.lastPathComponent == "sandboxReceipt" { 19 | return true 20 | } else { 21 | return false 22 | } 23 | } 24 | 25 | #if DEBUG 26 | public static var profilingStartTime: TimeInterval = 0 27 | public static var profilingTagTime: TimeInterval = 0 28 | #endif 29 | public static func startProfiling() { 30 | #if DEBUG 31 | profilingStartTime = Date().timeIntervalSince1970 32 | profilingTagTime = profilingStartTime 33 | NSLog("[Profiling] Start") 34 | #endif 35 | } 36 | public static func tagProfiling(_ message: String? = nil) { 37 | #if DEBUG 38 | guard profilingStartTime > 0 else { return } 39 | let now = Date().timeIntervalSince1970 40 | let elapsed = TimeInterval(Int((now - profilingStartTime) * 1000)) / 1000 41 | let elapsedFromLastTag = TimeInterval(Int((now - profilingTagTime) * 1000)) / 1000 42 | profilingTagTime = now 43 | NSLog("[Profiling][\(message ?? "")] Total: \(elapsed)s, Elapsed: \(elapsedFromLastTag)s") 44 | #endif 45 | } 46 | public static func stopProfiling() { 47 | #if DEBUG 48 | profilingStartTime = 0 49 | profilingTagTime = 0 50 | #endif 51 | } 52 | 53 | 54 | fileprivate static let queue = DispatchQueue(label: "TinyLog") 55 | } 56 | 57 | fileprivate class TinyLogDateFormatter { 58 | // MARK: Singleton 59 | fileprivate static let `default`: DateFormatter = { 60 | let formatter = DateFormatter() 61 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" 62 | return formatter 63 | }() 64 | } 65 | 66 | fileprivate func fileName(_ filePath: String) -> String { 67 | let lastPathComponent = NSString(string: filePath).lastPathComponent 68 | if let name = lastPathComponent.components(separatedBy: ".").first { 69 | return name 70 | } else { 71 | return lastPathComponent 72 | } 73 | } 74 | 75 | fileprivate func functionNameByStrippingParameters(_ function: String) -> String { 76 | if let startIndex = function.firstIndex(of: "(") { 77 | return String(function[.. { 149 | log("\(dict as AnyObject)", "⚫", file, function, line) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/AssetsPickerViewController/Classes/Photo/.DS_Store -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/Controller/AssetsPhotoViewController+AssetsManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoViewController+AssetsManager.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/02. 6 | // 7 | 8 | import UIKit 9 | import Photos 10 | 11 | // MARK: - AssetsManagerDelegate 12 | extension AssetsPhotoViewController: AssetsManagerDelegate { 13 | 14 | public func assetsManagerFetched(manager: AssetsManager) {} 15 | 16 | public func assetsManager(manager: AssetsManager, authorizationStatusChanged oldStatus: PHAuthorizationStatus, newStatus: PHAuthorizationStatus) { 17 | if #available(iOS 14, *) { 18 | if newStatus == .limited { 19 | updateNoPermissionView() 20 | AssetsManager.shared.fetchAssets(isRefetch: true, completion: { [weak self] (_) in 21 | DispatchQueue.main.async { [weak self] in 22 | self?.collectionView.reloadData() 23 | } 24 | }) 25 | } else { 26 | updateNoPermissionView() 27 | } 28 | } else { 29 | if oldStatus != .authorized { 30 | if newStatus == .authorized { 31 | updateNoPermissionView() 32 | AssetsManager.shared.fetchAssets(isRefetch: true, completion: { [weak self] (_) in 33 | DispatchQueue.main.async { [weak self] in 34 | self?.collectionView.reloadData() 35 | } 36 | }) 37 | } 38 | } else { 39 | updateNoPermissionView() 40 | } 41 | } 42 | } 43 | 44 | public func assetsManager(manager: AssetsManager, reloadedAlbumsInSection section: Int) {} 45 | public func assetsManager(manager: AssetsManager, insertedAlbums albums: [PHAssetCollection], at indexPaths: [IndexPath]) {} 46 | 47 | public func assetsManager(manager: AssetsManager, removedAlbums albums: [PHAssetCollection], at indexPaths: [IndexPath]) { 48 | logi("removedAlbums at indexPaths: \(indexPaths)") 49 | guard let selectedAlbum = manager.selectedAlbum else { 50 | logw("selected album is nil.") 51 | return 52 | } 53 | if albums.contains(selectedAlbum) { 54 | manager.selectDefaultAlbum() 55 | updateNavigationStatus() 56 | updateFooter() 57 | collectionView.reloadData() 58 | } 59 | } 60 | 61 | public func assetsManager(manager: AssetsManager, updatedAlbums albums: [PHAssetCollection], at indexPaths: [IndexPath]) {} 62 | public func assetsManager(manager: AssetsManager, reloadedAlbum album: PHAssetCollection, at indexPath: IndexPath) {} 63 | 64 | public func assetsManager(manager: AssetsManager, insertedAssets assets: [PHAsset], at indexPaths: [IndexPath]) { 65 | logi("insertedAssets at: \(indexPaths)") 66 | collectionView.insertItems(at: indexPaths) 67 | updateFooter() 68 | } 69 | 70 | public func assetsManager(manager: AssetsManager, removedAssets assets: [PHAsset], at indexPaths: [IndexPath]) { 71 | logi("removedAssets at: \(indexPaths)") 72 | for removedAsset in assets { 73 | if let index = selectedArray.firstIndex(of: removedAsset) { 74 | selectedArray.remove(at: index) 75 | selectedMap.removeValue(forKey: removedAsset.localIdentifier) 76 | } 77 | } 78 | collectionView.deleteItems(at: indexPaths) 79 | updateSelectionCount() 80 | updateNavigationStatus() 81 | updateFooter() 82 | } 83 | 84 | public func assetsManager(manager: AssetsManager, updatedAssets assets: [PHAsset], at indexPaths: [IndexPath]) { 85 | logi("updatedAssets at: \(indexPaths)") 86 | let indexPathsToReload = collectionView.indexPathsForVisibleItems.filter { indexPaths.contains($0) } 87 | 88 | collectionView.isUserInteractionEnabled = false 89 | selectNewlyAddedAssetIfNeeded { [weak self] (newlyaddedIndexPath) in 90 | if let indexPathToReload = indexPathsToReload.first, indexPathsToReload.count == 1, newlyaddedIndexPath == indexPathToReload { 91 | logd("Ignore newly added asset.") 92 | } else { 93 | self?.collectionView.reloadItems(at: indexPathsToReload) 94 | } 95 | self?.updateNavigationStatus() 96 | self?.updateFooter() 97 | self?.collectionView.isUserInteractionEnabled = true 98 | } 99 | } 100 | } 101 | 102 | extension AssetsPhotoViewController: AssetsPickerManagerDelegate { 103 | func assetsPickerManagerSavedAsset(identifier: String) { 104 | self.newlySavedIdentifier = identifier 105 | } 106 | } 107 | 108 | extension AssetsPhotoViewController { 109 | func selectNewlyAddedAssetIfNeeded(completion: @escaping ((IndexPath?) -> Void)) { 110 | var indexPathToSelect: IndexPath? 111 | 112 | if let newlySavedIdentifier = self.newlySavedIdentifier { 113 | self.newlySavedIdentifier = nil 114 | guard pickerConfig.assetIsAutoSelectAssetFromCamera else { 115 | completion(nil) 116 | return 117 | } 118 | var index: Int = NSNotFound 119 | guard let fetchResult = AssetsManager.shared.fetchResult else { return } 120 | fetchResult.enumerateObjects { (asset, idx, stop) in 121 | if asset.localIdentifier == newlySavedIdentifier { 122 | index = idx 123 | stop.pointee = true 124 | } 125 | } 126 | if index == NSNotFound { 127 | return 128 | } 129 | let ip = IndexPath(row: index, section: 0) 130 | indexPathToSelect = ip 131 | if selectedArray.count < pickerConfig.assetsMaximumSelectionCount { 132 | select(at: ip) 133 | } else { 134 | if pickerConfig.assetIsForcedSelectAssetFromCamera { 135 | select(at: ip) 136 | deselectOldestIfNeeded(isForced: true) 137 | updateSelectionCount() 138 | } 139 | } 140 | } 141 | if let indexPathToSelect = indexPathToSelect { 142 | DispatchQueue.main.asyncAfter(deadline: .now()) { [weak self] in 143 | guard let `self` = self else { return } 144 | self.selectCell(at: indexPathToSelect) 145 | if let addedCell = self.collectionView.cellForItem(at: indexPathToSelect) { 146 | if !self.collectionView.fullyVisibleCells.contains(addedCell) { 147 | self.collectionView.scrollToItem(at: indexPathToSelect, at: .bottom, animated: false) 148 | } 149 | } else { 150 | self.collectionView.scrollToItem(at: indexPathToSelect, at: .bottom, animated: false) 151 | } 152 | completion(indexPathToSelect) 153 | } 154 | } else { 155 | completion(nil) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/Controller/AssetsPhotoViewController+Collection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoViewController+Collection.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/03. 6 | // 7 | 8 | import UIKit 9 | import Photos 10 | 11 | // MARK: - UICollectionViewDataSource 12 | extension AssetsPhotoViewController: UICollectionViewDataSource { 13 | 14 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 15 | let count = AssetsManager.shared.fetchResult?.count ?? 0 16 | updateEmptyView(count: count) 17 | return count 18 | } 19 | 20 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 21 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath) 22 | guard var photoCell = cell as? AssetsPhotoCellProtocol else { 23 | logw("Failed to cast UICollectionViewCell.") 24 | return cell 25 | } 26 | if let asset = AssetsManager.shared.fetchResult?.object(at: indexPath.row) { 27 | photoCell.asset = asset 28 | photoCell.isVideo = asset.mediaType == .video 29 | if photoCell.isVideo { 30 | photoCell.duration = asset.duration 31 | } 32 | } else { 33 | photoCell.asset = nil 34 | } 35 | 36 | if #available(iOS 13.0, *) { 37 | let interaction = UIContextMenuInteraction(delegate: self) 38 | cell.addInteraction(interaction) 39 | } 40 | 41 | return cell 42 | } 43 | 44 | public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 45 | guard var photoCell = cell as? AssetsPhotoCellProtocol else { 46 | logw("Failed to cast UICollectionViewCell.") 47 | return 48 | } 49 | 50 | if let asset = AssetsManager.shared.fetchResult?.object(at: indexPath.row) { 51 | photoCell.asset = asset 52 | photoCell.isVideo = asset.mediaType == .video 53 | if photoCell.isVideo { 54 | photoCell.duration = asset.duration 55 | } 56 | 57 | if let selectedAsset = selectedMap[asset.localIdentifier] { 58 | if let targetIndex = selectedArray.firstIndex(of: selectedAsset) { 59 | photoCell.count = targetIndex + 1 60 | } 61 | } 62 | } else { 63 | photoCell.asset = nil 64 | } 65 | 66 | tryFetchImage(forCell: photoCell, forIndexPath: indexPath) 67 | 68 | if LogConfig.isCellLogEnabled { 69 | logd("[\(indexPath.row)] isSelected: \(photoCell.isSelected), isVideo: \(photoCell.isVideo), count: \(photoCell.count)") 70 | } 71 | } 72 | 73 | func tryFetchImage(forCell cell: AssetsPhotoCellProtocol, forIndexPath indexPath: IndexPath, isRetry: Bool = false) { 74 | let requestId = AssetsManager.shared.image(at: indexPath.row, size: pickerConfig.assetCacheSize, completion: { [weak self] (image, isDegraded) in 75 | guard let fetchService = self?.fetchService else { return } 76 | guard fetchService.isFetching(indexPath: indexPath) else { return } 77 | if !isDegraded { 78 | fetchService.removeFetching(indexPath: indexPath) 79 | } 80 | UIView.transition( 81 | with: cell.imageView, 82 | duration: 0.125, 83 | options: .transitionCrossDissolve, 84 | animations: { [weak self] in 85 | if let image = image, image.size.height > 0, image.size.height > 0 { 86 | cell.imageView.image = image 87 | } else { 88 | if !isRetry { 89 | self?.tryFetchImage(forCell: cell, forIndexPath: indexPath, isRetry: true) 90 | } else { 91 | logw("Failed to set right image at \(indexPath)") 92 | } 93 | } 94 | }, 95 | completion: nil 96 | ) 97 | }) 98 | fetchService.registerFetching(requestId: requestId, at: indexPath) 99 | } 100 | 101 | public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 102 | fetchService.cancelFetching(at: indexPath) 103 | } 104 | 105 | public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 106 | guard let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: footerReuseIdentifier, for: indexPath) as? AssetsPhotoFooterView else { 107 | logw("Failed to cast AssetsPhotoFooterView.") 108 | return AssetsPhotoFooterView() 109 | } 110 | footerView.setNeedsUpdateConstraints() 111 | footerView.updateConstraintsIfNeeded() 112 | footerView.set(imageCount: AssetsManager.shared.count(ofType: .image), videoCount: AssetsManager.shared.count(ofType: .video)) 113 | return footerView 114 | } 115 | } 116 | 117 | // MARK: - UICollectionViewDelegateFlowLayout 118 | extension AssetsPhotoViewController: UICollectionViewDelegateFlowLayout { 119 | 120 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { 121 | if collectionView.numberOfSections - 1 == section { 122 | if collectionView.bounds.width > collectionView.bounds.height { 123 | return CGSize(width: collectionView.bounds.width, height: pickerConfig.assetLandscapeCellSize(forViewSize: collectionView.bounds.size).width * 2/3) 124 | } else { 125 | return CGSize(width: collectionView.bounds.width, height: pickerConfig.assetPortraitCellSize(forViewSize: collectionView.bounds.size).width * 2/3) 126 | } 127 | } else { 128 | return .zero 129 | } 130 | } 131 | } 132 | 133 | // MARK: - UICollectionViewDataSourcePrefetching 134 | extension AssetsPhotoViewController: UICollectionViewDataSourcePrefetching { 135 | public func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { 136 | var assets = [PHAsset]() 137 | for indexPath in indexPaths { 138 | let count = AssetsManager.shared.fetchResult?.count ?? 0 139 | if count > indexPath.row { 140 | guard let asset = AssetsManager.shared.fetchResult?.object(at: indexPath.row) else { return } 141 | assets.append(asset) 142 | } 143 | } 144 | AssetsManager.shared.cache(assets: assets, size: pickerConfig.assetCacheSize) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/Controller/AssetsPhotoViewController+Delegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoViewController+Delegate.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/02. 6 | // 7 | 8 | import UIKit 9 | import Photos 10 | 11 | // MARK: - UI Event Handlers 12 | extension AssetsPhotoViewController { 13 | 14 | @objc func pressedCancel(button: UIBarButtonItem) { 15 | navigationController?.dismiss(animated: true, completion: { 16 | self.delegate?.assetsPicker?(controller: self.picker, didDismissByCancelling: true) 17 | }) 18 | delegate?.assetsPickerDidCancel?(controller: picker) 19 | } 20 | 21 | @objc func pressedCamera(button: UIBarButtonItem) { 22 | cameraPicker.requestTake(parent: self) 23 | } 24 | 25 | @objc func pressedDone(button: UIBarButtonItem) { 26 | navigationController?.dismiss(animated: true, completion: { 27 | self.delegate?.assetsPicker?(controller: self.picker, didDismissByCancelling: false) 28 | }) 29 | delegate?.assetsPicker(controller: picker, selected: selectedArray) 30 | } 31 | 32 | @objc func pressedTitle(gesture: UITapGestureRecognizer) { 33 | presentAlbumController() 34 | } 35 | } 36 | 37 | // MARK: - UIGestureRecognizerDelegate 38 | extension AssetsPhotoViewController: UIGestureRecognizerDelegate { 39 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 40 | guard let navigationBar = navigationController?.navigationBar else { return false } 41 | let point = touch.location(in: navigationBar) 42 | // Ignore touches on navigation buttons on both sides. 43 | return point.x > navigationBar.bounds.width / 4 && point.x < navigationBar.bounds.width * 3 / 4 44 | } 45 | } 46 | 47 | // MARK: - UIScrollViewDelegate 48 | extension AssetsPhotoViewController: UIScrollViewDelegate { 49 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 50 | // logi("contentOffset: \(scrollView.contentOffset)") 51 | updateCachedAssets() 52 | } 53 | } 54 | 55 | // MARK: - AssetsAlbumViewControllerDelegate 56 | extension AssetsPhotoViewController: AssetsAlbumViewControllerDelegate { 57 | 58 | public func assetsAlbumViewControllerCancelled(controller: AssetsAlbumViewController) { 59 | logi("Cancelled.") 60 | } 61 | 62 | public func assetsAlbumViewController(controller: AssetsAlbumViewController, selected album: PHAssetCollection) { 63 | select(album: album) 64 | } 65 | } 66 | 67 | @available(iOS 13.0, *) 68 | extension AssetsPhotoViewController: UIContextMenuInteractionDelegate { 69 | public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { 70 | return UIContextMenuConfiguration(identifier: nil, previewProvider: { [weak self] in 71 | guard let `self` = self else { return nil } 72 | let pointInCollectionView = self.collectionView.convert(location, from: interaction.view) 73 | guard let pressingIndexPath = self.collectionView.indexPathForItem(at: pointInCollectionView) else { return nil } 74 | let previewController = AssetsPreviewController() 75 | guard let fetchResult = AssetsManager.shared.fetchResult else { return nil } 76 | previewController.asset = fetchResult.object(at: pressingIndexPath.row) 77 | return previewController 78 | }, actionProvider: nil) 79 | } 80 | } 81 | 82 | // MARK - UIViewControllerPreviewingDelegate 83 | @available(iOS 9.0, *) 84 | extension AssetsPhotoViewController: UIViewControllerPreviewingDelegate { 85 | 86 | public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { 87 | logi("\(location)") 88 | guard !isDragSelectionEnabled else { return nil } 89 | guard let pressingIndexPath = collectionView.indexPathForItem(at: location) else { return nil } 90 | guard let pressingCell = collectionView.cellForItem(at: pressingIndexPath) else { return nil } 91 | previewingContext.sourceRect = pressingCell.frame 92 | let previewController = AssetsPreviewController() 93 | guard let fetchResult = AssetsManager.shared.fetchResult else { return nil } 94 | previewController.asset = fetchResult.object(at: pressingIndexPath.row) 95 | return previewController 96 | } 97 | 98 | public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { 99 | logi("viewControllerToCommit: \(type(of: viewControllerToCommit))") 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/Controller/AssetsPhotoViewController+Model.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoViewController+Model.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/03. 6 | // 7 | 8 | import UIKit 9 | import Photos 10 | 11 | extension AssetsPhotoViewController { 12 | 13 | func logSelectStatus(_ indexPath: IndexPath? = nil, isSelect: Bool) { 14 | if LogConfig.isSelectLogEnabled { 15 | if let indexPath = indexPath { 16 | logd("\(isSelect ? "Selected" : "Deselected") index: \(indexPath.row), count: \(selectedMap.count)") 17 | } else { 18 | logd("count: \(selectedMap.count)") 19 | } 20 | } 21 | } 22 | 23 | func setSelectedAssets(assets: [PHAsset]) { 24 | defer { logSelectStatus(isSelect: true) } 25 | 26 | selectedArray.removeAll() 27 | selectedMap.removeAll() 28 | 29 | _ = assets.filter { AssetsManager.shared.isExist(asset: $0) } 30 | .map { [weak self] asset in 31 | guard let `self` = self else { return } 32 | self.selectedArray.append(asset) 33 | self.selectedMap.updateValue(asset, forKey: asset.localIdentifier) 34 | } 35 | } 36 | 37 | func isSelected(at indexPath: IndexPath) -> Bool { 38 | let manager = AssetsManager.shared 39 | guard let fetchResult = manager.fetchResult else { return false } 40 | guard indexPath.row < fetchResult.count else { return false } 41 | let asset = fetchResult.object(at: indexPath.row) 42 | if let _ = selectedMap[asset.localIdentifier] { 43 | return true 44 | } else { 45 | return false 46 | } 47 | } 48 | 49 | func select(at indexPath: IndexPath) { 50 | defer { logSelectStatus(indexPath, isSelect: true) } 51 | let manager = AssetsManager.shared 52 | guard let fetchResult = manager.fetchResult else { return } 53 | guard indexPath.row < fetchResult.count else { return } 54 | let asset = fetchResult.object(at: indexPath.row) 55 | if let _ = selectedMap[asset.localIdentifier] {} else { 56 | selectedArray.append(asset) 57 | selectedMap[asset.localIdentifier] = asset 58 | } 59 | if let delegate = self.delegate { 60 | delegate.assetsPicker?(controller: picker, didSelect: asset, at: indexPath) 61 | } 62 | } 63 | 64 | func deselect(at indexPath: IndexPath) { 65 | defer { logSelectStatus(indexPath, isSelect: false) } 66 | let manager = AssetsManager.shared 67 | guard let fetchResult = manager.fetchResult else { return } 68 | guard indexPath.row < fetchResult.count else { return } 69 | let asset = fetchResult.object(at: indexPath.row) 70 | guard let targetAsset = selectedMap[asset.localIdentifier] else { 71 | logw("Invalid status.") 72 | return 73 | } 74 | guard let targetIndex = selectedArray.firstIndex(of: targetAsset) else { 75 | logw("Invalid status.") 76 | return 77 | } 78 | selectedArray.remove(at: targetIndex) 79 | selectedMap.removeValue(forKey: targetAsset.localIdentifier) 80 | 81 | if let delegate = self.delegate { 82 | delegate.assetsPicker?(controller: picker, didDeselect: asset, at: indexPath) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/Controller/AssetsPhotoViewController+Selection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoViewController+Selection.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/03. 6 | // 7 | 8 | import UIKit 9 | import Photos 10 | 11 | 12 | // MARK: - UICollectionViewDelegate 13 | extension AssetsPhotoViewController: UICollectionViewDelegate { 14 | @available(iOS 13.0, *) 15 | public func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool { 16 | return true 17 | } 18 | 19 | public func collectionView(_ collectionView: UICollectionView, didBeginMultipleSelectionInteractionAt indexPath: IndexPath) { 20 | isDragSelectionEnabled = true 21 | } 22 | 23 | public func collectionViewDidEndMultipleSelectionInteraction(_ collectionView: UICollectionView) { 24 | isDragSelectionEnabled = false 25 | } 26 | 27 | public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { 28 | return true 29 | } 30 | 31 | public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { 32 | if LogConfig.isSelectLogEnabled { logi("shouldSelectItemAt: \(indexPath.row)") } 33 | 34 | if let delegate = self.delegate { 35 | guard let fetchResult = AssetsManager.shared.fetchResult else { return false } 36 | let shouldSelect = delegate.assetsPicker?(controller: picker, shouldSelect: fetchResult.object(at: indexPath.row), at: indexPath) ?? true 37 | guard shouldSelect else { return false } 38 | } 39 | 40 | if isDragSelectionEnabled { 41 | if selectedArray.count < pickerConfig.assetsMaximumSelectionCount { 42 | select(at: indexPath) 43 | return true 44 | } else { 45 | return false 46 | } 47 | } else { 48 | select(at: indexPath) 49 | return true 50 | } 51 | } 52 | 53 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 54 | if LogConfig.isSelectLogEnabled { logi("didSelectItemAt: \(indexPath.row)") } 55 | if !isDragSelectionEnabled { 56 | deselectOldestIfNeeded() 57 | } 58 | updateSelectionCount() 59 | updateNavigationStatus() 60 | checkInconsistencyForSelection() 61 | } 62 | 63 | public func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool { 64 | if LogConfig.isSelectLogEnabled { logi("shouldDeselectItemAt: \(indexPath.row)") } 65 | if let delegate = self.delegate { 66 | guard let fetchResult = AssetsManager.shared.fetchResult else { return false } 67 | let shouldDeselect = delegate.assetsPicker?(controller: picker, shouldDeselect: fetchResult.object(at: indexPath.row), at: indexPath) ?? true 68 | guard shouldDeselect else { return false } 69 | } 70 | deselect(at: indexPath) 71 | return true 72 | } 73 | 74 | public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { 75 | if LogConfig.isSelectLogEnabled { logi("didDeselectItemAt: \(indexPath.row)") } 76 | updateSelectionCount() 77 | updateNavigationStatus() 78 | checkInconsistencyForSelection() 79 | } 80 | } 81 | 82 | extension AssetsPhotoViewController { 83 | func checkInconsistencyForSelection() { 84 | guard LogConfig.isSelectLogEnabled else { return } 85 | if let indexPathsForSelectedItems = collectionView.indexPathsForSelectedItems, !indexPathsForSelectedItems.isEmpty { 86 | if selectedArray.count != indexPathsForSelectedItems.count || selectedMap.count != indexPathsForSelectedItems.count { 87 | loge("selected item count not matched!") 88 | return 89 | } 90 | for selectedIndexPath in indexPathsForSelectedItems { 91 | guard let fetchResult = AssetsManager.shared.fetchResult else { return } 92 | if let _ = selectedMap[fetchResult.object(at: selectedIndexPath.row).localIdentifier] { 93 | 94 | } else { 95 | loge("selected item not found in local map!") 96 | } 97 | } 98 | } else { 99 | if !selectedMap.isEmpty || !selectedArray.isEmpty { 100 | loge("selected items not matched!") 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/Controller/AssetsPhotoViewController+Setup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoViewController+Setup.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/02. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - Initial Setups 11 | extension AssetsPhotoViewController { 12 | 13 | func setupCommon() { 14 | view.backgroundColor = .ap_background 15 | cameraPicker.delegate = self 16 | } 17 | 18 | func setupBarButtonItems() { 19 | navigationItem.leftBarButtonItem = cancelButtonItem 20 | if pickerConfig.assetIsShowCameraButton { 21 | navigationItem.rightBarButtonItems = [doneButtonItem, takeButtonItem] 22 | } else { 23 | navigationItem.rightBarButtonItems = [doneButtonItem] 24 | } 25 | doneButtonItem.isEnabled = false 26 | } 27 | 28 | func setupCollectionView() { 29 | 30 | collectionView.snp.makeConstraints { (make) in 31 | make.top.equalToSuperview() 32 | 33 | if #available(iOS 11.0, *) { 34 | leadingConstraint = make.leading.equalToSuperview().inset(view.safeAreaInsets.left).constraint.layoutConstraints.first 35 | trailingConstraint = make.trailing.equalToSuperview().inset(view.safeAreaInsets.right).constraint.layoutConstraints.first 36 | } else { 37 | leadingConstraint = make.leading.equalToSuperview().constraint.layoutConstraints.first 38 | trailingConstraint = make.trailing.equalToSuperview().constraint.layoutConstraints.first 39 | } 40 | make.bottom.equalToSuperview() 41 | } 42 | 43 | emptyView.snp.makeConstraints { (make) in 44 | make.edges.equalToSuperview() 45 | } 46 | 47 | noPermissionView.snp.makeConstraints { (make) in 48 | make.edges.equalToSuperview() 49 | } 50 | } 51 | 52 | func setupPlaceholderView() { 53 | loadingPlaceholderView.isHidden = true 54 | if #available(iOS 13.0, *) { 55 | loadingPlaceholderView.backgroundColor = .systemBackground 56 | } else { 57 | loadingPlaceholderView.backgroundColor = .white 58 | } 59 | loadingPlaceholderView.snp.makeConstraints { (make) in 60 | make.edges.equalToSuperview() 61 | } 62 | } 63 | 64 | func setupLoadActivityIndicatorView() { 65 | loadingActivityIndicatorView.snp.makeConstraints { (make) in 66 | make.center.equalToSuperview() 67 | } 68 | } 69 | 70 | func setupAssets() { 71 | loadingPlaceholderView.isHidden = false 72 | loadingActivityIndicatorView.startAnimating() 73 | let manager = AssetsManager.shared 74 | manager.subscribe(subscriber: self) 75 | manager.fetchAlbums { _ in 76 | manager.fetchAssets() { [weak self] result in 77 | guard let `self` = self else { return } 78 | guard let fetchResult = result else { return } 79 | self.updateEmptyView(count: fetchResult.count) 80 | self.updateNavigationStatus() 81 | self.collectionView.reloadData() 82 | self.preselectItemsIfNeeded(result: fetchResult) 83 | self.scrollToLastItemIfNeeded() 84 | self.updateCachedAssets(force: true) 85 | // hide loading 86 | self.loadingPlaceholderView.isHidden = true 87 | self.loadingActivityIndicatorView.stopAnimating() 88 | } 89 | } 90 | 91 | } 92 | 93 | func setupGestureRecognizer() { 94 | if let _ = self.tapGesture { 95 | // ignore 96 | } else { 97 | let gesture = UITapGestureRecognizer(target: self, action: #selector(pressedTitle)) 98 | navigationController?.navigationBar.addGestureRecognizer(gesture) 99 | gesture.delegate = self 100 | tapGesture = gesture 101 | } 102 | } 103 | 104 | func removeGestureRecognizer() { 105 | if let tapGesture = self.tapGesture { 106 | navigationController?.navigationBar.removeGestureRecognizer(tapGesture) 107 | self.tapGesture = nil 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/View/AssetsEmptyView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsEmptyView.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/26/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | open class AssetsEmptyView: AssetsGuideView { 12 | 13 | override func commonInit() { 14 | var messageKey = "Message_No_Items" 15 | if !UIImagePickerController.isCameraDeviceAvailable(.rear) { 16 | messageKey = "Message_No_Items_Camera" 17 | } 18 | set(title: String(key: "Title_No_Items"), message: String(format: String(key: messageKey), UIDevice.current.model)) 19 | titleStyle = .title2 20 | super.commonInit() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/View/AssetsFooterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoFooterView.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/18/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | open class AssetsPhotoFooterView: UICollectionReusableView { 12 | 13 | private let countLabel: UILabel = { 14 | let label = UILabel() 15 | label.textAlignment = .center 16 | label.font = UIFont.systemFont(forStyle: .subheadline, weight: .semibold) 17 | label.textColor = .ap_label 18 | return label 19 | }() 20 | 21 | public required init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | commonInit() 24 | } 25 | 26 | public override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | commonInit() 29 | } 30 | 31 | private func commonInit() { 32 | addSubview(countLabel) 33 | countLabel.snp.makeConstraints { (make) in 34 | make.edges.equalToSuperview() 35 | } 36 | } 37 | 38 | open func set(imageCount: Int, videoCount: Int) { 39 | var countText: String? 40 | if imageCount > 0 && videoCount > 0 { 41 | countText = String( 42 | format: String(key: "Footer_Items"), 43 | NumberFormatter.decimalString(value: imageCount), NumberFormatter.decimalString(value: videoCount)) 44 | } else if imageCount > 0 { 45 | countText = String( 46 | format: String(key: "Footer_Photos"), 47 | NumberFormatter.decimalString(value: imageCount)) 48 | } else if videoCount > 0 { 49 | countText = String( 50 | format: String(key: "Footer_Videos"), 51 | NumberFormatter.decimalString(value: videoCount)) 52 | } else { 53 | countText = String( 54 | format: String(key: "Footer_Items"), 55 | NumberFormatter.decimalString(value: 0), NumberFormatter.decimalString(value: 0)) 56 | } 57 | countLabel.text = countText 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/View/AssetsNoPermissionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsNoPermissionView.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/26/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | open class AssetsNoPermissionView: AssetsGuideView { 12 | 13 | override func commonInit() { 14 | set(title: String(key: "Title_No_Permission"), message: String(key: "Message_No_Permission")) 15 | super.commonInit() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/View/AssetsPhotoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoCell.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/17/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | public protocol AssetsPhotoCellProtocol { 13 | var asset: PHAsset? { get set } 14 | var isSelected: Bool { get set } 15 | var isVideo: Bool { get set } 16 | var imageView: UIImageView { get } 17 | var count: Int { set get } 18 | var duration: TimeInterval { set get } 19 | } 20 | 21 | open class AssetsPhotoCell: UICollectionViewCell, AssetsPhotoCellProtocol { 22 | 23 | // MARK: - AssetsPhotoCellProtocol 24 | open var asset: PHAsset? { 25 | didSet { 26 | // customizable 27 | if let asset = asset { 28 | panoramaIconView.isHidden = asset.mediaSubtypes != .photoPanorama 29 | } 30 | } 31 | } 32 | 33 | open var isVideo: Bool = false { 34 | didSet { 35 | durationLabel.isHidden = !isVideo 36 | if !isVideo { 37 | imageView.removeGradient() 38 | } 39 | } 40 | } 41 | 42 | open override var isSelected: Bool { 43 | didSet { overlay.isHidden = !isSelected } 44 | } 45 | 46 | public let imageView: UIImageView = { 47 | let view = UIImageView() 48 | view.backgroundColor = UIColor(rgbHex: 0xF0F0F0) 49 | view.contentMode = .scaleAspectFill 50 | view.clipsToBounds = true 51 | return view 52 | }() 53 | 54 | open var count: Int = 0 { 55 | didSet { overlay.countLabel.text = "\(count)" } 56 | } 57 | 58 | open var duration: TimeInterval = 0 { 59 | didSet { 60 | durationLabel.text = String(duration: duration) 61 | } 62 | } 63 | 64 | // MARK: - Views 65 | private let durationLabel: UILabel = { 66 | let label = UILabel() 67 | label.textColor = .white 68 | label.textAlignment = .right 69 | label.font = UIFont.systemFont(forStyle: .caption1) 70 | return label 71 | }() 72 | 73 | private let panoramaIconView: PanoramaIconView = { 74 | let view = PanoramaIconView() 75 | view.isHidden = true 76 | return view 77 | }() 78 | 79 | private let overlay: AssetsPhotoCellOverlay = { 80 | let overlay = AssetsPhotoCellOverlay() 81 | overlay.isHidden = true 82 | overlay.isUserInteractionEnabled = false 83 | return overlay 84 | }() 85 | 86 | // MARK: - Lifecycle 87 | public required init?(coder aDecoder: NSCoder) { 88 | super.init(coder: aDecoder) 89 | commonInit() 90 | } 91 | 92 | public override init(frame: CGRect) { 93 | super.init(frame: frame) 94 | commonInit() 95 | } 96 | 97 | private func commonInit() { 98 | contentView.addSubview(imageView) 99 | contentView.addSubview(durationLabel) 100 | contentView.addSubview(panoramaIconView) 101 | contentView.addSubview(overlay) 102 | 103 | imageView.snp.makeConstraints { (make) in 104 | make.edges.equalToSuperview() 105 | } 106 | 107 | durationLabel.snp.makeConstraints { (make) in 108 | make.height.equalTo(durationLabel.font.pointSize + 10) 109 | make.leading.equalToSuperview().offset(8) 110 | make.trailing.equalToSuperview().inset(8) 111 | make.bottom.equalToSuperview() 112 | } 113 | 114 | panoramaIconView.snp.makeConstraints { (make) in 115 | make.size.equalTo(CGSize(width: 14, height: 7)) 116 | make.trailing.equalToSuperview().inset(6.5) 117 | make.bottom.equalToSuperview().inset(10) 118 | } 119 | 120 | overlay.snp.makeConstraints { (make) in 121 | make.edges.equalToSuperview() 122 | } 123 | } 124 | 125 | open override func layoutSubviews() { 126 | super.layoutSubviews() 127 | guard isVideo else { return } 128 | imageView.setGradient(.fromBottom, start: 0, end: 0.2, startAlpha: 0.75, color: .black) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/View/AssetsPhotoCellOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoCellOverlay.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/26/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | open class AssetsPhotoCellOverlay: UIView { 12 | 13 | open var count: Int = 0 { 14 | didSet { countLabel.text = "\(count)" } 15 | } 16 | 17 | // MARK: - Views 18 | 19 | let countLabel: UILabel = { 20 | let label = UILabel() 21 | label.textAlignment = .center 22 | label.textColor = UIColor.white 23 | label.adjustsFontSizeToFitWidth = true 24 | label.font = UIFont.systemFont(forStyle: .subheadline) 25 | label.isHidden = true 26 | return label 27 | }() 28 | 29 | let checkmark: SSCheckMark = { 30 | let view = SSCheckMark() 31 | return view 32 | }() 33 | 34 | // MARK: - Lifecycle 35 | public required init?(coder aDecoder: NSCoder) { 36 | super.init(coder: aDecoder) 37 | commonInit() 38 | } 39 | 40 | public override init(frame: CGRect) { 41 | super.init(frame: frame) 42 | commonInit() 43 | } 44 | 45 | private func commonInit() { 46 | dmr_dim(animated: false, color: .white, alpha: 0.25) 47 | addSubview(countLabel) 48 | addSubview(checkmark) 49 | 50 | countLabel.snp.makeConstraints { (make) in 51 | make.edges.equalToSuperview() 52 | } 53 | checkmark.snp.makeConstraints { (make) in 54 | make.size.equalTo(CGSize(width: 30, height: 30)) 55 | make.bottom.equalToSuperview().inset(1) 56 | make.trailing.equalToSuperview().inset(1) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/View/AssetsPhotoLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPhotoLayout.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/18/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | open class AssetsPhotoLayout: UICollectionViewFlowLayout { 12 | 13 | open var translatedOffset: CGPoint? 14 | fileprivate var pickerConfig: AssetsPickerConfig 15 | 16 | public required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | public init(pickerConfig: AssetsPickerConfig) { 21 | self.pickerConfig = pickerConfig 22 | super.init() 23 | } 24 | 25 | open override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 26 | return targetContentOffset(forProposedContentOffset: proposedContentOffset) 27 | } 28 | 29 | open override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint { 30 | if let translatedOffset = self.translatedOffset { 31 | return translatedOffset 32 | } else { 33 | return proposedContentOffset 34 | } 35 | } 36 | } 37 | 38 | extension AssetsPhotoLayout { 39 | 40 | open func expectedContentHeight(forViewSize size: CGSize, isPortrait: Bool) -> CGFloat { 41 | guard let fetchResult = AssetsManager.shared.fetchResult else { return 0.0 } 42 | var rows = fetchResult.count / (isPortrait ? pickerConfig.assetPortraitColumnCount : pickerConfig.assetLandscapeColumnCount) 43 | let remainder = fetchResult.count % (isPortrait ? pickerConfig.assetPortraitColumnCount : pickerConfig.assetLandscapeColumnCount) 44 | rows += remainder > 0 ? 1 : 0 45 | 46 | let cellSize = isPortrait ? pickerConfig.assetPortraitCellSize(forViewSize: UIScreen.main.portraitContentSize) : pickerConfig.assetLandscapeCellSize(forViewSize: UIScreen.main.landscapeContentSize) 47 | let lineSpace = isPortrait ? pickerConfig.assetPortraitLineSpace : pickerConfig.assetLandscapeLineSpace 48 | let contentHeight = CGFloat(rows) * cellSize.height + (CGFloat(max(rows - 1, 0)) * lineSpace) 49 | let bottomHeight = cellSize.height * 2/3 + UIScreen.safeAreaInsets(isPortrait: isPortrait).bottom 50 | 51 | return contentHeight + bottomHeight 52 | } 53 | 54 | private func offsetRatio(collectionView: UICollectionView, offset: CGPoint, contentSize: CGSize, isPortrait: Bool) -> CGFloat { 55 | return (offset.y > 0 ? offset.y : 0) / ((contentSize.height + UIScreen.safeAreaInsets(isPortrait: isPortrait).bottom) - collectionView.bounds.height) 56 | } 57 | 58 | open func translateOffset(forChangingSize size: CGSize, currentOffset: CGPoint) -> CGPoint? { 59 | guard let collectionView = self.collectionView else { 60 | return nil 61 | } 62 | let isPortraitFuture = size.height > size.width 63 | let isPortraitCurrent = collectionView.bounds.size.height > collectionView.bounds.size.width 64 | let contentHeight = expectedContentHeight(forViewSize: size, isPortrait: isPortraitFuture) 65 | let currentRatio = offsetRatio(collectionView: collectionView, offset: currentOffset, contentSize: collectionView.contentSize, isPortrait: isPortraitCurrent) 66 | logi("currentRatio = \(currentRatio)") 67 | var futureOffsetY = (contentHeight - size.height) * currentRatio 68 | 69 | if currentOffset.y < 0 { 70 | let insetRatio = (-currentOffset.y) / UIScreen.safeAreaInsets(isPortrait: isPortraitCurrent).top 71 | let insetDiff = UIScreen.safeAreaInsets(isPortrait: isPortraitFuture).top * insetRatio 72 | futureOffsetY -= insetDiff 73 | } 74 | 75 | return CGPoint(x: 0, y: futureOffsetY) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Photo/View/PanoramaIconView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PanoramaIconView.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 05/01/2018. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | open class PanoramaIconView: UIView { 12 | 13 | private var iconLayer: CAShapeLayer? = nil 14 | 15 | open var iconColor: UIColor = .white 16 | 17 | public required init?(coder aDecoder: NSCoder) { 18 | super.init(coder: aDecoder) 19 | } 20 | 21 | public override init(frame: CGRect) { 22 | super.init(frame: frame) 23 | } 24 | 25 | public convenience init(frame: CGRect, color: UIColor) { 26 | self.init(frame: frame) 27 | self.iconColor = color 28 | } 29 | 30 | override open func layoutSubviews() { 31 | super.layoutSubviews() 32 | if let iconLayer = self.iconLayer { 33 | iconLayer.removeFromSuperlayer() 34 | self.iconLayer = nil 35 | } 36 | let iconLayer = shapeLayer(forPath: shapePath(forSize: bounds.size)) 37 | layer.addSublayer(iconLayer) 38 | self.iconLayer = iconLayer 39 | } 40 | 41 | private func shapePath(forSize size: CGSize) -> UIBezierPath { 42 | 43 | let intensity: CGFloat = 0.44 44 | let controlRatio: CGFloat = 0.25 45 | 46 | let padding: CGFloat = 0 47 | let leftTop: CGPoint = CGPoint(x: padding, y: padding) 48 | let rightTop: CGPoint = CGPoint(x: size.width - padding, y: padding) 49 | let leftBottom: CGPoint = CGPoint(x: padding, y: size.height - padding) 50 | let rightBottom: CGPoint = CGPoint(x: size.width - padding, y: size.height - padding) 51 | 52 | let path = UIBezierPath() 53 | path.move(to: leftTop) 54 | path.addQuadCurve( 55 | to: CGPoint(x: size.width / 2, y: size.height / 2 * intensity), 56 | controlPoint: CGPoint(x: size.width / 2 * controlRatio, y: size.height / 2 * intensity)) 57 | path.addQuadCurve( 58 | to: rightTop, 59 | controlPoint: CGPoint(x: size.width - (size.width / 2 * controlRatio), y: size.height / 2 * intensity)) 60 | path.addLine(to: rightBottom) 61 | path.addQuadCurve( 62 | to: CGPoint(x: size.width / 2, y: size.height - (size.height / 2 * intensity)), 63 | controlPoint: CGPoint(x: size.width - (size.width / 2 * controlRatio), y: size.height - (size.height / 2 * intensity))) 64 | path.addQuadCurve( 65 | to: leftBottom, 66 | controlPoint: CGPoint(x: size.width / 2 * controlRatio, y: size.height - (size.height / 2 * intensity))) 67 | path.addLine(to: leftTop) 68 | path.close() 69 | return path 70 | } 71 | 72 | private func shapeLayer(forPath path: UIBezierPath) -> CAShapeLayer { 73 | let shapeLayer = CAShapeLayer() 74 | shapeLayer.path = path.cgPath 75 | shapeLayer.strokeColor = iconColor.cgColor 76 | shapeLayer.fillColor = iconColor.cgColor 77 | return shapeLayer 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Picker/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCherry/AssetsPickerViewController/12b6cc1917638cd35e3a5f5492e77b94b00b0eb9/AssetsPickerViewController/Classes/Picker/.DS_Store -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Picker/AssetsCameraManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsCameraManager.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/02. 6 | // 7 | 8 | import Foundation 9 | import AVFoundation 10 | import Photos 11 | import UIKit 12 | 13 | protocol AssetsPickerManagerDelegate: NSObject { 14 | func assetsPickerManagerSavedAsset(identifier: String) 15 | } 16 | 17 | class AssetsPickerManager: NSObject { 18 | 19 | fileprivate var successCallback: ((Any?) -> Void)? 20 | fileprivate var cancelCallback: (() -> Void)? 21 | 22 | private let allowsEditing: Bool = true 23 | fileprivate var savedLocalIdentifier: String? 24 | 25 | var isAutoSave: Bool = true 26 | weak var delegate: AssetsPickerManagerDelegate? 27 | 28 | func requestTakePhoto(parent: UIViewController, success: ((Any?) -> Void)? = nil, cancel: (() -> Void)? = nil) { 29 | let controller = UIImagePickerController() 30 | controller.delegate = self 31 | controller.sourceType = .camera 32 | controller.allowsEditing = allowsEditing 33 | self.successCallback = success 34 | self.cancelCallback = cancel 35 | parent.present(controller, animated: true, completion: nil) 36 | } 37 | 38 | func requestTake(parent: UIViewController, success: ((Any?) -> Void)? = nil, cancel: (() -> Void)? = nil) { 39 | let controller = UIImagePickerController() 40 | controller.delegate = self 41 | controller.sourceType = .camera 42 | controller.allowsEditing = allowsEditing 43 | controller.mediaTypes = ["public.image", "public.movie"] 44 | self.successCallback = success 45 | self.cancelCallback = cancel 46 | parent.present(controller, animated: true, completion: nil) 47 | } 48 | 49 | func requestImage(parent: UIViewController, success: ((Any?) -> Void)? = nil, cancel: (() -> Void)? = nil) { 50 | let controller = UIImagePickerController() 51 | controller.delegate = self 52 | controller.sourceType = .photoLibrary 53 | controller.allowsEditing = allowsEditing 54 | self.successCallback = success 55 | self.cancelCallback = cancel 56 | parent.present(controller, animated: true, completion: nil) 57 | } 58 | } 59 | 60 | extension AssetsPickerManager: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 61 | 62 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 63 | // Local variable inserted by Swift 4.2 migrator. 64 | let info = convertFromUIImagePickerControllerInfoKeyDictionary(info) 65 | 66 | picker.dismiss(animated: true, completion: { [weak self] in 67 | guard let `self` = self else { return } 68 | 69 | var mediaType: PHAssetMediaType = .unknown 70 | 71 | if let lowercasedMediaType = (info[convertFromUIImagePickerControllerInfoKey(.mediaType)] as? String)?.lowercased() { 72 | if lowercasedMediaType.contains("image") { 73 | mediaType = .image 74 | } else if lowercasedMediaType.contains("movie") { 75 | mediaType = .video 76 | } 77 | } 78 | 79 | switch mediaType { 80 | case .image: 81 | guard let image = (info[convertFromUIImagePickerControllerInfoKey(.editedImage)] as? UIImage) ?? (info[convertFromUIImagePickerControllerInfoKey(.originalImage)] as? UIImage) else { 82 | self.successCallback?(nil) 83 | return 84 | } 85 | if self.isAutoSave { 86 | PHPhotoLibrary.shared().performChanges({ 87 | let request = PHAssetChangeRequest.creationRequestForAsset(from: image) 88 | if let identifier = request.placeholderForCreatedAsset?.localIdentifier { 89 | self.savedLocalIdentifier = identifier 90 | self.successCallback?(image) 91 | } 92 | }) { [weak self] (isSuccess, _) in 93 | if let localIdentifier = self?.savedLocalIdentifier, isSuccess { 94 | self?.savedLocalIdentifier = nil 95 | self?.delegate?.assetsPickerManagerSavedAsset(identifier: localIdentifier) 96 | } 97 | self?.successCallback?(image) 98 | } 99 | } else { 100 | self.successCallback?(image) 101 | } 102 | case .video: 103 | guard let videoFileURL = info[convertFromUIImagePickerControllerInfoKey(.mediaURL)] as? URL else { 104 | self.successCallback?(nil) 105 | return 106 | } 107 | if self.isAutoSave { 108 | PHPhotoLibrary.shared().performChanges({ 109 | if let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoFileURL), let identifier = request.placeholderForCreatedAsset?.localIdentifier { 110 | self.savedLocalIdentifier = identifier 111 | self.successCallback?(videoFileURL) 112 | } else { 113 | self.successCallback?(videoFileURL) 114 | } 115 | }) { [weak self] (isSuccess, _) in 116 | if let localIdentifier = self?.savedLocalIdentifier, isSuccess { 117 | self?.savedLocalIdentifier = nil 118 | self?.delegate?.assetsPickerManagerSavedAsset(identifier: localIdentifier) 119 | } 120 | self?.successCallback?(videoFileURL) 121 | } 122 | } else { 123 | self.successCallback?(videoFileURL) 124 | } 125 | default: 126 | self.successCallback?(nil) 127 | } 128 | }) 129 | } 130 | 131 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 132 | picker.dismiss(animated: true, completion: { [weak self] in 133 | self?.cancelCallback?() 134 | }) 135 | } 136 | } 137 | 138 | fileprivate func convertFromUIImagePickerControllerInfoKeyDictionary(_ input: [UIImagePickerController.InfoKey: Any]) -> [String: Any] { 139 | return Dictionary(uniqueKeysWithValues: input.map {key, value in (key.rawValue, value)}) 140 | } 141 | 142 | fileprivate func convertFromUIImagePickerControllerInfoKey(_ input: UIImagePickerController.InfoKey) -> String { 143 | return input.rawValue 144 | } 145 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Picker/AssetsPickerCustomStringConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPickerCustomStringConfig.swift 3 | // Pods 4 | // 5 | // Created by Zonily Jame Pesquera on 5/22/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias AssetsPickerCustomStringConfig = [AssetsPickerLocalizedStringKey: String] 12 | 13 | // These are the available string keys on AssetsPickerViewController.strings 14 | public enum AssetsPickerLocalizedStringKey: String { 15 | case cancel = "Cancel" 16 | case done = "Done" 17 | case titleAlbums = "Title_Albums" 18 | case titleSectionMyAlbums = "Title_Section_MyAlbums" 19 | case footerPhotos = "Footer_Photos" 20 | case footerVideos = "Footer_Videos" 21 | case footerItems = "Footer_Items" 22 | case titleSelectedPhoto = "Title_Selected_Photo" 23 | case titleSelectedPhotos = "Title_Selected_Photos" 24 | case titleSelectedVideo = "Title_Selected_Video" 25 | case titleSelectedVideos = "Title_Selected_Videos" 26 | case titleSelectedItems = "Title_Selected_Items" 27 | case titleNoItems = "Title_No_Items" 28 | case messageNoItems = "Message_No_Items" 29 | case messageNoItemsCamera = "Message_No_Items_Camera" 30 | case titleNoPermission = "Title_No_Permission" 31 | case messageNoPermission = "Message_No_Permission" 32 | } 33 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Picker/Controller/AssetsPickerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsPickerViewController.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/17/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | // MARK: - AssetsPickerViewControllerDelegate 13 | @objc public protocol AssetsPickerViewControllerDelegate: class { 14 | @objc optional func assetsPickerDidCancel(controller: AssetsPickerViewController) 15 | @objc optional func assetsPickerCannotAccessPhotoLibrary(controller: AssetsPickerViewController) 16 | func assetsPicker(controller: AssetsPickerViewController, selected assets: [PHAsset]) 17 | @objc optional func assetsPicker(controller: AssetsPickerViewController, shouldSelect asset: PHAsset, at indexPath: IndexPath) -> Bool 18 | @objc optional func assetsPicker(controller: AssetsPickerViewController, didSelect asset: PHAsset, at indexPath: IndexPath) 19 | @objc optional func assetsPicker(controller: AssetsPickerViewController, shouldDeselect asset: PHAsset, at indexPath: IndexPath) -> Bool 20 | @objc optional func assetsPicker(controller: AssetsPickerViewController, didDeselect asset: PHAsset, at indexPath: IndexPath) 21 | @objc optional func assetsPicker(controller: AssetsPickerViewController, didDismissByCancelling byCancel: Bool) 22 | } 23 | 24 | // MARK: - AssetsPickerViewController 25 | @objcMembers 26 | open class AssetsPickerViewController: UINavigationController { 27 | 28 | @objc open weak var pickerDelegate: AssetsPickerViewControllerDelegate? 29 | open var selectedAssets: [PHAsset] { 30 | return photoViewController.selectedArray 31 | } 32 | 33 | open var isShowLog: Bool = false { 34 | didSet { 35 | TinyLog.isShowInfoLog = isShowLog 36 | TinyLog.isShowErrorLog = isShowLog 37 | } 38 | } 39 | public var pickerConfig: AssetsPickerConfig! { 40 | didSet { 41 | if let config = self.pickerConfig?.prepare() { 42 | AssetsManager.shared.pickerConfig = config 43 | photoViewController?.pickerConfig = config 44 | } 45 | } 46 | } 47 | 48 | public private(set) var photoViewController: AssetsPhotoViewController! 49 | 50 | required public init?(coder aDecoder: NSCoder) { 51 | super.init(coder: aDecoder) 52 | commonInit() 53 | } 54 | 55 | override public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 56 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 57 | commonInit() 58 | } 59 | 60 | public convenience init() { 61 | self.init(nibName: nil, bundle: nil) 62 | } 63 | 64 | func commonInit() { 65 | let config = AssetsPickerConfig().prepare() 66 | self.pickerConfig = config 67 | AssetsManager.shared.pickerConfig = config 68 | let controller = AssetsPhotoViewController() 69 | controller.pickerConfig = config 70 | self.photoViewController = controller 71 | 72 | viewControllers = [photoViewController] 73 | } 74 | 75 | deinit { 76 | AssetsManager.shared.clear() 77 | logd("Released \(type(of: self))") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Picker/LogConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogConfig.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/03. 6 | // 7 | 8 | import Foundation 9 | 10 | class LogConfig { 11 | static var isFetchLogEnabled = true 12 | static var isCellLogEnabled = false 13 | static var isSelectLogEnabled = false 14 | static var isAlbumImageSizeLogEnabled = false 15 | static var isAlbumCellLogEnabled = false 16 | } 17 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Picker/View/AssetsGuideView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsGuideView.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/26/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | open class AssetsGuideView: UIView { 12 | 13 | var lineSpace: CGFloat = 10 14 | var titleStyle: UIFont.TextStyle = .title1 15 | var bodyStyle: UIFont.TextStyle = .body 16 | 17 | fileprivate lazy var messageLabel: UILabel = { 18 | let label = UILabel() 19 | label.textAlignment = .center 20 | label.numberOfLines = 10 21 | return label 22 | }() 23 | 24 | public required init?(coder aDecoder: NSCoder) { 25 | super.init(coder: aDecoder) 26 | commonInit() 27 | } 28 | 29 | public override init(frame: CGRect) { 30 | super.init(frame: frame) 31 | commonInit() 32 | } 33 | 34 | func commonInit() { 35 | addSubview(messageLabel) 36 | messageLabel.snp.makeConstraints { (make) in 37 | make.top.equalToSuperview() 38 | make.leading.equalToSuperview().inset(15) 39 | make.bottom.equalToSuperview() 40 | make.trailing.equalToSuperview().inset(15) 41 | } 42 | } 43 | 44 | open func set(title: String, message: String) { 45 | 46 | let attributedString = NSMutableAttributedString() 47 | 48 | let titleParagraphStyle = NSMutableParagraphStyle() 49 | titleParagraphStyle.paragraphSpacing = lineSpace 50 | titleParagraphStyle.alignment = .center 51 | let attributedTitle = NSMutableAttributedString(string: "\(title)\n", attributes: [ 52 | NSAttributedString.Key.font: UIFont.systemFont(forStyle: titleStyle), 53 | NSAttributedString.Key.foregroundColor: UIColor.ap_secondaryLabel, 54 | NSAttributedString.Key.paragraphStyle: titleParagraphStyle 55 | ]) 56 | 57 | let bodyParagraphStyle = NSMutableParagraphStyle() 58 | bodyParagraphStyle.alignment = .center 59 | bodyParagraphStyle.firstLineHeadIndent = 20 60 | bodyParagraphStyle.headIndent = 20 61 | bodyParagraphStyle.tailIndent = -20 62 | let attributedBody = NSMutableAttributedString(string: message, attributes: [ 63 | NSAttributedString.Key.font: UIFont.systemFont(forStyle: bodyStyle), 64 | NSAttributedString.Key.foregroundColor: UIColor.ap_secondaryLabel, 65 | NSAttributedString.Key.paragraphStyle: bodyParagraphStyle 66 | ]) 67 | 68 | attributedString.append(attributedTitle) 69 | attributedString.append(attributedBody) 70 | messageLabel.attributedText = attributedString 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Picker/View/SSCheckMark.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSCheckMark.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/26/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | enum SSCheckMarkStyle { 12 | case openCircle 13 | case grayedOut 14 | } 15 | 16 | open class SSCheckMark: UIView { 17 | 18 | open var isChecked: Bool = true { 19 | didSet { setNeedsDisplay() } 20 | } 21 | 22 | var checkMarkStyle: SSCheckMarkStyle = .grayedOut { 23 | didSet { setNeedsDisplay() } 24 | } 25 | 26 | public required init?(coder aDecoder: NSCoder) { 27 | super.init(coder: aDecoder) 28 | commonInit() 29 | } 30 | 31 | public override init(frame: CGRect) { 32 | super.init(frame: frame) 33 | commonInit() 34 | } 35 | 36 | private func commonInit() { 37 | backgroundColor = .clear 38 | } 39 | 40 | open override func draw(_ rect: CGRect) { 41 | super.draw(rect) 42 | if isChecked { 43 | drawRectChecked(rect: rect) 44 | } else { 45 | switch checkMarkStyle { 46 | case .openCircle: 47 | drawRectOpenCircle(rect: rect) 48 | case .grayedOut: 49 | drawRectGrayedOut(rect: rect) 50 | } 51 | } 52 | } 53 | 54 | func drawRectChecked(rect: CGRect) { 55 | guard let context = UIGraphicsGetCurrentContext() else { return } 56 | 57 | let checkmarkColor = AssetsPickerConfig.defaultCheckmarkColor 58 | let shadow2 = UIColor.black 59 | 60 | let shadow2Offset = CGSize(width: 0.1, height: -0.1) 61 | let shadow2BlurRadius = 2.5 62 | let frame = self.bounds 63 | let group = CGRect(x: frame.minX + 3, y: frame.minY + 3, width: frame.width - 6, height: frame.height - 6) 64 | 65 | let checkedOvalPath = UIBezierPath(ovalIn: CGRect(x: group.minX + floor(group.width * 0.00000 + 0.5), y: group.minY + floor(group.height * 0.00000 + 0.5), width: floor(group.width * 1.00000 + 0.5) - floor(group.width * 0.00000 + 0.5), height: floor(group.height * 1.00000 + 0.5) - floor(group.height * 0.00000 + 0.5))) 66 | 67 | context.saveGState() 68 | context.setShadow(offset: shadow2Offset, blur: CGFloat(shadow2BlurRadius), color: shadow2.cgColor) 69 | checkmarkColor.setFill() 70 | checkedOvalPath.fill() 71 | context.restoreGState() 72 | UIColor.white.setStroke() 73 | checkedOvalPath.lineWidth = 1 74 | checkedOvalPath.stroke() 75 | let bezierPath = UIBezierPath() 76 | bezierPath.move(to: CGPoint(x: group.minX + 0.27083 * group.width, y: group.minY + 0.54167 * group.height)) 77 | bezierPath.addLine(to: CGPoint(x: group.minX + 0.41667 * group.width, y: group.minY + 0.68750 * group.height)) 78 | bezierPath.addLine(to: CGPoint(x: group.minX + 0.75000 * group.width, y: group.minY + 0.35417 * group.height)) 79 | bezierPath.lineCapStyle = CGLineCap.square 80 | UIColor.white.setStroke() 81 | bezierPath.lineWidth = 1.3 82 | bezierPath.stroke() 83 | } 84 | 85 | 86 | func drawRectGrayedOut(rect: CGRect) { 87 | guard let context = UIGraphicsGetCurrentContext() else { return } 88 | 89 | let grayTranslucent = UIColor(red: 1, green: 1, blue: 1, alpha: 0.6) 90 | let shadow2 = UIColor.black 91 | let shadow2Offset = CGSize(width: 0.1, height: -0.1) 92 | let shadow2BlurRadius = 2.5 93 | let frame = self.bounds 94 | let group = CGRect(x: frame.minX + 3, y: frame.minY + 3, width: frame.width - 6, height: frame.height - 6) 95 | let uncheckedOvalPath = UIBezierPath(ovalIn: CGRect(x: group.minX + floor(group.width * 0.00000 + 0.5), y: group.minY + floor(group.height * 0.00000 + 0.5), width: floor(group.width * 1.00000 + 0.5) - floor(group.width * 0.00000 + 0.5), height: floor(group.height * 1.00000 + 0.5) - floor(group.height * 0.00000 + 0.5))) 96 | 97 | context.saveGState() 98 | context.setShadow(offset: shadow2Offset, blur: CGFloat(shadow2BlurRadius), color: shadow2.cgColor) 99 | grayTranslucent.setFill() 100 | uncheckedOvalPath.fill() 101 | context.restoreGState() 102 | UIColor.white.setStroke() 103 | uncheckedOvalPath.lineWidth = 1 104 | uncheckedOvalPath.stroke() 105 | let bezierPath = UIBezierPath() 106 | 107 | bezierPath.move(to: CGPoint(x: group.minX + 0.27083 * group.width, y: group.minY + 0.54167 * group.height)) 108 | bezierPath.addLine(to: CGPoint(x: group.minX + 0.41667 * group.width, y: group.minY + 0.68750 * group.height)) 109 | bezierPath.addLine(to: CGPoint(x: group.minX + 0.75000 * group.width, y: group.minY + 0.35417 * group.height)) 110 | bezierPath.lineCapStyle = CGLineCap.square 111 | UIColor.white.setStroke() 112 | bezierPath.lineWidth = 1.3 113 | bezierPath.stroke() 114 | } 115 | 116 | func drawRectOpenCircle(rect: CGRect) { 117 | guard let context = UIGraphicsGetCurrentContext() else { return } 118 | 119 | let shadow = UIColor.black 120 | let shadowOffset = CGSize(width: 0.1, height: -0.1) 121 | let shadowBlurRadius = 0.5 122 | let shadow2 = UIColor.black 123 | let shadow2Offset = CGSize(width: 0.1, height: -0.1) 124 | let shadow2BlurRadius = 2.5 125 | let frame = self.bounds 126 | let group = CGRect(x: frame.minX + 3, y: frame.minY + 3, width: frame.width - 6, height: frame.height - 6) 127 | let emptyOvalPath = UIBezierPath(ovalIn: CGRect(x: group.minX + floor(group.width * 0.00000 + 0.5), y: group.minY + floor(group.height * 0.00000 + 0.5), width: floor(group.width * 1.00000 + 0.5) - floor(group.width * 0.00000 + 0.5), height: floor(group.height * 1.00000 + 0.5) - floor(group.height * 0.00000 + 0.5))) 128 | 129 | context.saveGState() 130 | context.setShadow(offset: shadow2Offset, blur: CGFloat(shadow2BlurRadius), color: shadow2.cgColor) 131 | 132 | context.restoreGState() 133 | context.saveGState() 134 | context.setShadow(offset: shadowOffset, blur: CGFloat(shadowBlurRadius), color: shadow.cgColor) 135 | UIColor.white.setStroke() 136 | emptyOvalPath.lineWidth = 1 137 | emptyOvalPath.stroke() 138 | context.restoreGState() 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Utility/Bundle+AssetsPickerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+AssetsPickerViewController.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension Bundle { 12 | 13 | private static let kAssetsPickerResourceName = "AssetsPickerViewController" 14 | private static let kAssetsPickerResourceType = "bundle" 15 | 16 | static let assetsRootBundle: Bundle = { 17 | return Bundle(for: AssetsPickerViewController.classForCoder()) 18 | }() 19 | 20 | static let assetsPickerPath: String? = { 21 | return Bundle.assetsRootBundle.path(forResource: Bundle.kAssetsPickerResourceName, ofType: Bundle.kAssetsPickerResourceType) 22 | }() 23 | 24 | static var assetsPickerBundle: Bundle { 25 | if let path = assetsPickerPath { 26 | if let bundle = Bundle(path: path) { 27 | return bundle 28 | } else { 29 | logw("Failed to get localized bundle.") 30 | } 31 | } 32 | return assetsRootBundle 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Utility/NumberFormatter+AssetsPickerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberFormatter+AssetsPickerViewController.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/19/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension NumberFormatter { 12 | 13 | static func decimalString(value: Int) -> String { 14 | let formatter = NumberFormatter() 15 | formatter.numberStyle = .decimal 16 | formatter.locale = Locale.current 17 | return formatter.string(from: NSNumber(value: value)) ?? "" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Utility/String+AssetsPickerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+AssetsPickerViewController.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | init(key: String) { 14 | guard 15 | let customConfig = AssetsPickerConfig.customStringConfig, 16 | let localizedKey = AssetsPickerLocalizedStringKey(rawValue: key), 17 | let string = customConfig[localizedKey] else { 18 | #if SWIFT_PACKAGE 19 | self = Bundle.module.localizedString(forKey: key, value: key, table: "AssetsPickerViewController") 20 | #else 21 | self = Bundle.assetsPickerBundle.localizedString(forKey: key, value: key, table: "AssetsPickerViewController") 22 | #endif 23 | return 24 | } 25 | self = string 26 | } 27 | 28 | init(duration: TimeInterval) { 29 | let hour = Int(duration / 3600) 30 | let min = Int((duration / 60).truncatingRemainder(dividingBy: 60)) 31 | let sec = Int(duration.truncatingRemainder(dividingBy: 60)) 32 | var durationString = hour > 0 ? "\(hour)" : "" 33 | durationString.append(min > 0 ? "\(min):" : "0:") 34 | durationString.append(String(format: "%02d", sec)) 35 | self = durationString 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Utility/UIColor+AssetsPickerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+AssetsPickerViewController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 07/10/2019. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | static var ap_label: UIColor { 14 | if #available(iOS 13.0, *) { 15 | return .label 16 | } else { 17 | return .black 18 | } 19 | } 20 | 21 | static var ap_secondaryLabel: UIColor { 22 | if #available(iOS 13.0, *) { 23 | return .secondaryLabel 24 | } else { 25 | return UIColor(rgbHex: 0x8C8C91) 26 | } 27 | } 28 | 29 | static var ap_background: UIColor { 30 | if #available(iOS 13.0, *) { 31 | return .systemBackground 32 | } else { 33 | return .white 34 | } 35 | } 36 | 37 | static var ap_cellBackground: UIColor { 38 | if #available(iOS 13.0, *) { 39 | return .secondarySystemBackground 40 | } else { 41 | return UIColor(rgbHex: 0xF0F0F0) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Utility/UIFont+AssetsPickerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+AssetsPickerViewController.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/19/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIFont { 12 | static func systemFont(forStyle style: UIFont.TextStyle, weight: Weight = UIFont.Weight.regular) -> UIFont { 13 | let font = UIFont.preferredFont(forTextStyle: style) 14 | return UIFont.systemFont(ofSize: font.pointSize, weight: weight) 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /AssetsPickerViewController/Classes/Utility/UIScreen+AssetsPickerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreen+AssetsPickerViewController.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 5/19/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIScreen { 12 | static func safeAreaInsets(isPortrait: Bool) -> UIEdgeInsets { 13 | let w: Double = Double(UIScreen.main.bounds.width) 14 | let h: Double = Double(UIScreen.main.bounds.height) 15 | let screenHeight: Double = max(w, h) 16 | 17 | switch screenHeight { 18 | case 812: // 5.8" (iPhone X/XS/XR/11) 19 | return isPortrait ? UIEdgeInsets(top: 88, left: 0, bottom: 34, right: 0) : UIEdgeInsets(top: 32, left: 44, bottom: 21, right: 44) 20 | default: 21 | return isPortrait ? UIEdgeInsets(top: 64, left: 0, bottom: 0, right: 0) : UIEdgeInsets(top: 30, left: 0, bottom: 0, right: 0) 22 | } 23 | } 24 | 25 | var portraitSize: CGSize { 26 | let size = UIScreen.main.bounds.size 27 | return CGSize(width: min(size.width, size.height), height: max(size.width, size.height)) 28 | } 29 | 30 | var landscapeSize: CGSize { 31 | let size = UIScreen.main.bounds.size 32 | return CGSize(width: max(size.width, size.height), height: min(size.width, size.height)) 33 | } 34 | 35 | var portraitContentSize: CGSize { 36 | var size = UIScreen.main.portraitSize 37 | if #available(iOS 11.0, *) { 38 | size.width -= UIScreen.safeAreaInsets(isPortrait: true).left + UIScreen.safeAreaInsets(isPortrait: true).right 39 | size.height -= UIScreen.safeAreaInsets(isPortrait: true).top + UIScreen.safeAreaInsets(isPortrait: true).bottom 40 | } 41 | return size 42 | } 43 | 44 | var landscapeContentSize: CGSize { 45 | var size = UIScreen.main.landscapeSize 46 | if #available(iOS 11.0, *) { 47 | size.width -= UIScreen.safeAreaInsets(isPortrait: false).left + UIScreen.safeAreaInsets(isPortrait: false).right 48 | size.height -= UIScreen.safeAreaInsets(isPortrait: false).top + UIScreen.safeAreaInsets(isPortrait: false).bottom 49 | } 50 | return size 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Common/AssetsFetchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetsFetchService.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 2020/07/03. 6 | // 7 | 8 | import Photos 9 | 10 | // MARK: - Image Fetching IDs 11 | class AssetsFetchService { 12 | 13 | var requestMap = [IndexPath: PHImageRequestID]() 14 | 15 | func cancelFetching(at indexPath: IndexPath) { 16 | if let requestId = requestMap[indexPath] { 17 | requestMap.removeValue(forKey: indexPath) 18 | if LogConfig.isFetchLogEnabled { logd("Canceled ID: \(requestId) at: \(indexPath.row) (\(self.requestMap.count))") } 19 | AssetsManager.shared.cancelRequest(requestId: requestId) 20 | } 21 | } 22 | 23 | func registerFetching(requestId: PHImageRequestID, at indexPath: IndexPath) { 24 | requestMap[indexPath] = requestId 25 | if LogConfig.isFetchLogEnabled { logd("Registered ID: \(requestId) at: \(indexPath.row) (\(self.requestMap.count))") } 26 | } 27 | 28 | func removeFetching(indexPath: IndexPath) { 29 | if let requestId = requestMap[indexPath] { 30 | requestMap.removeValue(forKey: indexPath) 31 | if LogConfig.isFetchLogEnabled { logd("Removed ID: \(requestId) at: \(indexPath.row) (\(self.requestMap.count))") } 32 | } 33 | } 34 | 35 | func isFetching(indexPath: IndexPath) -> Bool { 36 | if let _ = requestMap[indexPath] { 37 | return true 38 | } else { 39 | return false 40 | } 41 | } 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /Common/SwiftAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftAlert.swift 3 | // Pods 4 | // 5 | // Created by DragonCherry on 1/10/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | @available(iOS 8.0, *) 12 | @objcMembers public class SwiftAlert: NSObject { 13 | 14 | fileprivate weak var parent: UIViewController? 15 | fileprivate var alertController: UIAlertController? 16 | fileprivate var forceDismiss: Bool = false 17 | public var isShowing: Bool = false 18 | 19 | fileprivate var dismissHandler: ((Int) -> Void)? 20 | fileprivate var cancelHandler: (() -> Void)? 21 | fileprivate var destructHandler: (() -> Void)? 22 | 23 | fileprivate var promptHandler: ((Int, String?) -> Void)? 24 | fileprivate var promptTextField: UITextField? 25 | 26 | public required override init() { 27 | super.init() 28 | } 29 | 30 | public func show( 31 | _ parent: UIViewController, 32 | style: UIAlertController.Style = .alert, 33 | title: String? = nil, 34 | message: String?, 35 | dismissTitle: String) { 36 | 37 | self.show( 38 | parent, 39 | style: style, 40 | title: title, 41 | message: message, 42 | cancelTitle: nil, 43 | cancel: nil, 44 | otherTitles: [dismissTitle], 45 | dismiss: nil) 46 | } 47 | 48 | public func show( 49 | _ parent: UIViewController, 50 | style: UIAlertController.Style = .alert, 51 | title: String? = nil, 52 | message: String?, 53 | dismissTitle: String, 54 | dismiss: (() -> Void)?) { 55 | 56 | self.show( 57 | parent, 58 | style: style, 59 | title: title, 60 | message: message, 61 | cancelTitle: dismissTitle, 62 | cancel: { dismiss?() }, 63 | otherTitles: nil, 64 | dismiss: nil) 65 | } 66 | 67 | public func show( 68 | _ parent: UIViewController, 69 | style: UIAlertController.Style = .alert, 70 | title: String? = nil, 71 | message: String? = nil, 72 | cancelTitle: String? = nil, 73 | cancel: (() -> Void)? = nil, 74 | otherTitles: [String]? = nil, 75 | dismiss: ((Int) -> Void)? = nil, 76 | destructTitle: String? = nil, 77 | destruct: (() -> Void)? = nil) { 78 | 79 | close(false) 80 | 81 | self.parent = parent 82 | self.dismissHandler = dismiss 83 | self.cancelHandler = cancel 84 | self.destructHandler = destruct 85 | 86 | self.alertController = UIAlertController(title: title, message: message, preferredStyle: style) 87 | guard let alertController = self.alertController else { 88 | return 89 | } 90 | 91 | if let otherTitles = otherTitles { 92 | for (index, otherTitle) in otherTitles.enumerated() { 93 | let dismissAction: UIAlertAction = UIAlertAction(title: otherTitle, style: .default, handler: { action in 94 | self.dismissHandler?(index) 95 | self.clear() 96 | }) 97 | alertController.addAction(dismissAction) 98 | } 99 | } 100 | 101 | if let cancelTitle = cancelTitle { 102 | let cancelAction: UIAlertAction = UIAlertAction(title: cancelTitle, style: .cancel, handler: { action in 103 | self.cancelHandler?() 104 | self.clear() 105 | }) 106 | alertController.addAction(cancelAction) 107 | } 108 | 109 | if let destructTitle = destructTitle { 110 | let destructAction: UIAlertAction = UIAlertAction(title: destructTitle, style: .destructive, handler: { action in 111 | self.destructHandler?() 112 | self.clear() 113 | }) 114 | alertController.addAction(destructAction) 115 | } 116 | 117 | if let parent = self.parent { 118 | parent.present(alertController, animated: true, completion: nil) 119 | } else { 120 | print("Cannot find parent while presenting alert.") 121 | } 122 | 123 | self.isShowing = true 124 | } 125 | 126 | public func prompt( 127 | 128 | _ parent: UIViewController, 129 | title: String? = nil, 130 | message: String?, 131 | placeholder: String? = nil, 132 | defaultText: String? = nil, 133 | isNumberOnly: Bool = false, 134 | isSecure: Bool = true, 135 | cancelTitle: String? = nil, 136 | cancel: (() -> Void)? = nil, 137 | otherTitles: [String]? = nil, 138 | prompt: ((Int, String?) -> Void)? = nil) { 139 | 140 | close(false) 141 | 142 | self.parent = parent 143 | self.promptHandler = prompt 144 | self.cancelHandler = cancel 145 | 146 | self.alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 147 | guard let alertController = self.alertController else { 148 | return 149 | } 150 | 151 | alertController.addTextField(configurationHandler: { textField in 152 | if isNumberOnly { 153 | textField.keyboardType = .numberPad 154 | } 155 | textField.text = defaultText 156 | textField.isSecureTextEntry = isSecure 157 | if let placeholder = placeholder { 158 | textField.placeholder = placeholder 159 | } 160 | self.promptTextField = textField 161 | }) 162 | 163 | if let otherTitles = otherTitles { 164 | for (index, otherTitle) in otherTitles.enumerated() { 165 | let dismissAction: UIAlertAction = UIAlertAction(title: otherTitle, style: .default, handler: { action in 166 | self.promptTextField?.resignFirstResponder() 167 | self.promptHandler?(index, self.promptTextField!.text) 168 | self.clear() 169 | }) 170 | alertController.addAction(dismissAction) 171 | } 172 | } 173 | 174 | if let cancelTitle = cancelTitle { 175 | let cancelAction: UIAlertAction = UIAlertAction(title: cancelTitle, style: .cancel, handler: { action in 176 | self.promptTextField?.resignFirstResponder() 177 | self.cancelHandler?() 178 | self.clear() 179 | }) 180 | alertController.addAction(cancelAction) 181 | } 182 | 183 | if let parent = self.parent { 184 | parent.present(alertController, animated: true, completion: nil) 185 | } else { 186 | print("Cannot find parent while presenting alert.") 187 | } 188 | 189 | isShowing = true 190 | } 191 | 192 | fileprivate func autoDismiss() { 193 | close() 194 | } 195 | 196 | public func close(_ animated: Bool = true, completion: (() -> Void)? = nil) { 197 | if isShowing { 198 | alertController?.dismiss(animated: animated, completion: { 199 | completion?() 200 | }) 201 | } 202 | clear() 203 | isShowing = false 204 | } 205 | 206 | private func clear() { 207 | self.parent = nil 208 | self.alertController = nil 209 | self.promptTextField = nil 210 | self.cancelHandler = nil 211 | self.dismissHandler = nil 212 | self.destructHandler = nil 213 | } 214 | } 215 | 216 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController.xcodeproj/xcshareddata/xcschemes/AssetsPickerViewController-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 78 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 05/17/2017. 6 | // Copyright (c) 2017 DragonCherry. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/Common/CommonExampleController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonExampleController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 5/29/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | import AssetsPickerViewController 12 | 13 | class CommonExampleController: UITableViewController { 14 | 15 | let kCellReuseIdentifier: String = UUID().uuidString 16 | lazy var imageManager = { 17 | return PHCachingImageManager() 18 | }() 19 | lazy var cellSize: CGSize = { 20 | return CGSize(width: self.view.bounds.width, height: 60) 21 | }() 22 | var assets = [PHAsset]() 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | tableView.tableFooterView = UIView() 27 | tableView.dataSource = self 28 | tableView.delegate = self 29 | tableView.rowHeight = cellSize.height 30 | title = "\(type(of: self))" 31 | setupToolbarItems() 32 | } 33 | 34 | func setupToolbarItems() { 35 | let clearItem = UIBarButtonItem(title: "Clear", style: .plain, target: self, action: #selector(pressedClear)) 36 | let spaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 37 | let pickItem = UIBarButtonItem(title: "Pick", style: .plain, target: self, action: #selector(pressedPick)) 38 | toolbarItems = [clearItem, spaceItem, pickItem] 39 | } 40 | 41 | override func viewWillAppear(_ animated: Bool) { 42 | super.viewWillAppear(animated) 43 | navigationController?.setToolbarHidden(false, animated: true) 44 | } 45 | 46 | override func viewWillDisappear(_ animated: Bool) { 47 | super.viewWillDisappear(animated) 48 | navigationController?.setToolbarHidden(true, animated: true) 49 | } 50 | 51 | // MARK: - Delegates 52 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 53 | return assets.count 54 | } 55 | 56 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 57 | guard let cell = tableView.dequeueReusableCell(withIdentifier: kCellReuseIdentifier) else { 58 | return UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: kCellReuseIdentifier) 59 | } 60 | return cell 61 | } 62 | 63 | override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 64 | let asset = assets[indexPath.row] 65 | 66 | // set title & summary 67 | cell.textLabel?.text = "[\(asset.mediaType == .image ? "Photo" : "Video")] \(asset.pixelWidth)x\(asset.pixelHeight)" 68 | cell.detailTextLabel?.text = "\(asset.creationDate?.description ?? "Unknown")" 69 | 70 | // set image 71 | let imageWidth = cellSize.height * UIScreen.main.scale 72 | imageManager.requestImage(for: asset, targetSize: CGSize(width: imageWidth, height: imageWidth), contentMode: .aspectFill, options: nil) { (image, info) in 73 | cell.imageView?.contentMode = .scaleAspectFill 74 | cell.imageView?.image = image 75 | cell.setNeedsLayout() 76 | cell.layoutIfNeeded() 77 | } 78 | } 79 | 80 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 81 | tableView.deselectRow(at: indexPath, animated: true) 82 | } 83 | } 84 | 85 | extension CommonExampleController { 86 | @objc func pressedClear(_ sender: Any) { 87 | assets.removeAll() 88 | tableView.reloadData() 89 | } 90 | @objc func pressedPick(_ sender: Any) {} 91 | } 92 | 93 | extension CommonExampleController: AssetsPickerViewControllerDelegate { 94 | 95 | func assetsPickerCannotAccessPhotoLibrary(controller: AssetsPickerViewController) { 96 | logw("Need permission to access photo library.") 97 | } 98 | 99 | func assetsPickerDidCancel(controller: AssetsPickerViewController) { 100 | logi("Cancelled.") 101 | } 102 | 103 | func assetsPicker(controller: AssetsPickerViewController, selected assets: [PHAsset]) { 104 | self.assets = assets 105 | tableView.reloadData() 106 | } 107 | 108 | func assetsPicker(controller: AssetsPickerViewController, shouldSelect asset: PHAsset, at indexPath: IndexPath) -> Bool { 109 | logi("shouldSelect: \(indexPath.row)") 110 | 111 | // can limit selection count 112 | if controller.selectedAssets.count > 3 { 113 | // do your job here 114 | } 115 | return true 116 | } 117 | 118 | func assetsPicker(controller: AssetsPickerViewController, didSelect asset: PHAsset, at indexPath: IndexPath) { 119 | logi("didSelect: \(indexPath.row)") 120 | } 121 | 122 | func assetsPicker(controller: AssetsPickerViewController, shouldDeselect asset: PHAsset, at indexPath: IndexPath) -> Bool { 123 | logi("shouldDeselect: \(indexPath.row)") 124 | return true 125 | } 126 | 127 | func assetsPicker(controller: AssetsPickerViewController, didDeselect asset: PHAsset, at indexPath: IndexPath) { 128 | logi("didDeselect: \(indexPath.row)") 129 | } 130 | 131 | func assetsPicker(controller: AssetsPickerViewController, didDismissByCancelling byCancel: Bool) { 132 | logi("dismiss completed - byCancel: \(byCancel)") 133 | } 134 | } 135 | 136 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/AlbumFilterBlockController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumFilterBlockController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 6/2/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AssetsPickerViewController 11 | import Photos 12 | 13 | class AlbumFilterBlockController: CommonExampleController { 14 | 15 | override func pressedPick(_ sender: Any) { 16 | 17 | let pickerConfig = AssetsPickerConfig() 18 | 19 | let smartAlbumFilter: ((PHAssetCollection, PHFetchResult) -> Bool) = { (album, fetchResult) in 20 | 21 | // filter by album object 22 | if album.assetCollectionSubtype == .smartAlbumBursts { return false } 23 | if album.assetCollectionSubtype == .smartAlbumTimelapses { return false } 24 | if album.assetCollectionSubtype == .smartAlbumFavorites { return false } 25 | 26 | // filter by fetch result 27 | if fetchResult.count > 50 { 28 | return true // only shows albums that contains more than 50 assets 29 | } else { 30 | return false 31 | } 32 | } 33 | pickerConfig.albumFilter = [ 34 | .smartAlbum: smartAlbumFilter 35 | ] 36 | 37 | let picker = AssetsPickerViewController() 38 | picker.pickerConfig = pickerConfig 39 | picker.pickerDelegate = self 40 | present(picker, animated: true, completion: nil) 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/AlbumFilterOptionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumFilterOptionController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 6/2/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AssetsPickerViewController 11 | import Photos 12 | 13 | class AlbumFilterOptionController: CommonExampleController { 14 | 15 | override func pressedPick(_ sender: Any) { 16 | 17 | let pickerConfig = AssetsPickerConfig() 18 | 19 | let options = PHFetchOptions() 20 | options.predicate = NSPredicate(format: "estimatedAssetCount = 0") 21 | 22 | pickerConfig.albumFetchOptions = [ 23 | .smartAlbum: options // apply to smart albums only 24 | ] 25 | 26 | let picker = AssetsPickerViewController() 27 | picker.pickerConfig = pickerConfig 28 | picker.pickerDelegate = self 29 | present(picker, animated: true, completion: nil) 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/AlbumOrderBlockController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumOrderBlockController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 6/2/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AssetsPickerViewController 11 | 12 | class AlbumOrderBlockController: CommonExampleController { 13 | 14 | override func pressedPick(_ sender: Any) { 15 | 16 | let pickerConfig = AssetsPickerConfig() 17 | 18 | // Priority of this option is higher than PHFetchOptions. 19 | pickerConfig.albumComparator = { (albumType, leftEntry, rightEntry) -> Bool in 20 | 21 | // return: Is leftEntry ordered before the rightEntry? 22 | switch albumType { 23 | case .smartAlbum: 24 | return leftEntry.album.assetCollectionSubtype.rawValue < rightEntry.album.assetCollectionSubtype.rawValue 25 | case .album: 26 | return leftEntry.result.count < rightEntry.result.count // ascending order by asset count 27 | case .moment: 28 | return true 29 | @unknown default: 30 | fatalError() 31 | } 32 | } 33 | 34 | let picker = AssetsPickerViewController() 35 | picker.pickerConfig = pickerConfig 36 | picker.pickerDelegate = self 37 | present(picker, animated: true, completion: nil) 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/AlbumOrderOptionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumOrderOptionController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 6/2/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AssetsPickerViewController 11 | import Photos 12 | 13 | class AlbumOrderOptionController: CommonExampleController { 14 | 15 | override func pressedPick(_ sender: Any) { 16 | 17 | let pickerConfig = AssetsPickerConfig() 18 | 19 | let options = PHFetchOptions() 20 | options.sortDescriptors = [NSSortDescriptor(key: "estimatedAssetCount", ascending: true)] 21 | 22 | pickerConfig.albumFetchOptions = [ 23 | .smartAlbum: options 24 | ] 25 | 26 | let picker = AssetsPickerViewController() 27 | picker.pickerConfig = pickerConfig 28 | picker.pickerDelegate = self 29 | present(picker, animated: true, completion: nil) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/AssetFilterOptionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetFilterOptionController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 6/2/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AssetsPickerViewController 11 | import Photos 12 | 13 | class AssetFilterOptionController: CommonExampleController { 14 | 15 | override func pressedPick(_ sender: Any) { 16 | 17 | let pickerConfig = AssetsPickerConfig() 18 | 19 | let options = PHFetchOptions() 20 | options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue) 21 | options.sortDescriptors = [NSSortDescriptor(key: "duration", ascending: true)] 22 | 23 | pickerConfig.assetFetchOptions = [ 24 | .smartAlbum: options, 25 | .album: options 26 | ] 27 | 28 | let picker = AssetsPickerViewController() 29 | picker.pickerConfig = pickerConfig 30 | picker.pickerDelegate = self 31 | present(picker, animated: true, completion: nil) 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/AssetOrderOptionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetOrderOptionController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 6/2/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AssetsPickerViewController 11 | import Photos 12 | 13 | class AssetOrderOptionController: CommonExampleController { 14 | 15 | override func pressedPick(_ sender: Any) { 16 | 17 | let pickerConfig = AssetsPickerConfig() 18 | 19 | let options = PHFetchOptions() 20 | options.sortDescriptors = [ 21 | NSSortDescriptor(key: "pixelWidth", ascending: true), 22 | NSSortDescriptor(key: "pixelHeight", ascending: true) 23 | ] 24 | 25 | pickerConfig.assetFetchOptions = [ 26 | .smartAlbum: options 27 | ] 28 | 29 | let picker = AssetsPickerViewController() 30 | picker.pickerConfig = pickerConfig 31 | picker.pickerDelegate = self 32 | present(picker, animated: true, completion: nil) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/BasicUsageController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicUsageController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 5/17/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import AssetsPickerViewController 10 | 11 | class BasicUsageController: CommonExampleController { 12 | 13 | override func pressedPick(_ sender: Any) { 14 | let picker = AssetsPickerViewController() 15 | picker.isShowLog = true 16 | picker.pickerDelegate = self 17 | present(picker, animated: true, completion: nil) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/CustomAlbumCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomAlbumCellController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 5/29/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | import AssetsPickerViewController 12 | 13 | private let imageSize = CGSize(width: 80, height: 80) 14 | 15 | class CustomAlbumCell: UICollectionViewCell, AssetsAlbumCellProtocol { 16 | 17 | // MARK: - AssetsAlbumCellProtocol 18 | var album: PHAssetCollection? { 19 | didSet {} 20 | } 21 | 22 | override var isSelected: Bool { 23 | didSet { 24 | if isSelected { 25 | contentView.dmr_dim(animated: false, color: .gray, alpha: 0.3) 26 | } else { 27 | contentView.dmr_undim() 28 | } 29 | } 30 | } 31 | 32 | var imageView: UIImageView = { 33 | let view = UIImageView() 34 | view.clipsToBounds = true 35 | view.contentMode = .scaleAspectFill 36 | view.backgroundColor = UIColor(rgbHex: 0xF0F0F0) 37 | return view 38 | }() 39 | 40 | var titleText: String? { 41 | didSet { 42 | if let titleText = self.titleText { 43 | titleLabel.text = "\(titleText) (\(count))" 44 | } else { 45 | titleLabel.text = nil 46 | } 47 | } 48 | } 49 | 50 | var count: Int = 0 { 51 | didSet { 52 | if let titleText = self.titleText { 53 | titleLabel.text = "\(titleText) (\(count))" 54 | } else { 55 | titleLabel.text = nil 56 | } 57 | } 58 | } 59 | 60 | // MARK: - At your service 61 | 62 | var titleLabel: UILabel = { 63 | let label = UILabel() 64 | label.clipsToBounds = true 65 | return label 66 | }() 67 | 68 | required init?(coder aDecoder: NSCoder) { 69 | super.init(coder: aDecoder) 70 | commonInit() 71 | } 72 | override init(frame: CGRect) { 73 | super.init(frame: frame) 74 | commonInit() 75 | } 76 | 77 | private func commonInit() { 78 | contentView.addSubview(imageView) 79 | contentView.addSubview(titleLabel) 80 | 81 | imageView.snp.makeConstraints { (make) in 82 | make.size.equalTo(imageSize) 83 | make.leading.equalToSuperview() 84 | } 85 | titleLabel.snp.makeConstraints { (make) in 86 | make.leading.equalTo(imageView.snp.trailing).inset(10) 87 | make.top.equalToSuperview() 88 | make.bottom.equalToSuperview() 89 | make.trailing.equalToSuperview() 90 | } 91 | } 92 | } 93 | 94 | class CustomAlbumCellController: CommonExampleController { 95 | 96 | override func pressedPick(_ sender: Any) { 97 | 98 | let pickerConfig = AssetsPickerConfig() 99 | pickerConfig.albumCellType = CustomAlbumCell.classForCoder() 100 | pickerConfig.albumPortraitForcedCellHeight = imageSize.height 101 | pickerConfig.albumLandscapeForcedCellHeight = imageSize.height 102 | pickerConfig.albumForcedCacheSize = imageSize 103 | pickerConfig.albumDefaultSpace = 0 104 | pickerConfig.albumLineSpace = 1 105 | pickerConfig.albumPortraitColumnCount = 1 106 | pickerConfig.albumLandscapeColumnCount = 1 107 | 108 | let picker = AssetsPickerViewController() 109 | picker.pickerConfig = pickerConfig 110 | picker.pickerDelegate = self 111 | 112 | present(picker, animated: true, completion: nil) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/CustomAssetCellController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomAssetCellController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 5/31/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | import AssetsPickerViewController 12 | 13 | class CustomAssetCellOverlay: UIView { 14 | 15 | private let countSize = CGSize(width: 40, height: 40) 16 | 17 | lazy var circleView: UIView = { 18 | let view = UIView() 19 | view.backgroundColor = .black 20 | view.layer.cornerRadius = self.countSize.width / 2 21 | view.alpha = 0.4 22 | return view 23 | }() 24 | let countLabel: UILabel = { 25 | let label = UILabel() 26 | let font = UIFont.preferredFont(forTextStyle: .headline) 27 | label.font = UIFont.systemFont(ofSize: font.pointSize, weight: UIFont.Weight.bold) 28 | label.textAlignment = .center 29 | label.textColor = .white 30 | label.adjustsFontSizeToFitWidth = true 31 | return label 32 | }() 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | super.init(coder: aDecoder) 36 | commonInit() 37 | } 38 | override init(frame: CGRect) { 39 | super.init(frame: frame) 40 | commonInit() 41 | } 42 | 43 | private func commonInit() { 44 | dmr_dim(animated: false, color: .white, alpha: 0.25) 45 | addSubview(circleView) 46 | addSubview(countLabel) 47 | 48 | circleView.snp.makeConstraints { (make) in 49 | make.size.equalTo(countSize) 50 | make.center.equalToSuperview() 51 | } 52 | 53 | countLabel.snp.makeConstraints { (make) in 54 | make.size.equalTo(countSize) 55 | make.center.equalToSuperview() 56 | } 57 | } 58 | } 59 | 60 | class CustomAssetCell: UICollectionViewCell, AssetsPhotoCellProtocol { 61 | 62 | // MARK: - AssetsAlbumCellProtocol 63 | var asset: PHAsset? { 64 | didSet {} 65 | } 66 | 67 | var isVideo: Bool = false { 68 | didSet { 69 | durationLabel.isHidden = !isVideo 70 | } 71 | } 72 | 73 | override var isSelected: Bool { 74 | didSet { overlay.isHidden = !isSelected } 75 | } 76 | 77 | var imageView: UIImageView = { 78 | let view = UIImageView() 79 | view.clipsToBounds = true 80 | view.contentMode = .scaleAspectFill 81 | view.backgroundColor = UIColor(rgbHex: 0xF0F0F0) 82 | return view 83 | }() 84 | 85 | var count: Int = 0 { 86 | didSet { overlay.countLabel.text = "\(count)" } 87 | } 88 | 89 | var duration: TimeInterval = 0 { 90 | didSet { 91 | let hour = Int(duration / 3600) 92 | let min = Int((duration / 60).truncatingRemainder(dividingBy: 60)) 93 | let sec = Int(duration.truncatingRemainder(dividingBy: 60)) 94 | var durationString = hour > 0 ? "\(hour)" : "" 95 | durationString.append(min > 0 ? "\(min):" : "0:") 96 | durationString.append(String(format: "%02d", sec)) 97 | durationLabel.text = durationString 98 | } 99 | } 100 | 101 | // MARK: - At your service 102 | 103 | let overlay: CustomAssetCellOverlay = { 104 | let view = CustomAssetCellOverlay() 105 | view.isHidden = true 106 | return view 107 | }() 108 | 109 | private let durationLabel: UILabel = { 110 | let label = UILabel() 111 | label.textColor = .white 112 | label.textAlignment = .right 113 | label.font = UIFont.boldSystemFont(ofSize: 20) 114 | return label 115 | }() 116 | 117 | required init?(coder aDecoder: NSCoder) { 118 | super.init(coder: aDecoder) 119 | commonInit() 120 | } 121 | override init(frame: CGRect) { 122 | super.init(frame: frame) 123 | commonInit() 124 | } 125 | 126 | private func commonInit() { 127 | contentView.addSubview(imageView) 128 | contentView.addSubview(overlay) 129 | contentView.addSubview(durationLabel) 130 | 131 | imageView.snp.makeConstraints { (make) in 132 | make.edges.equalToSuperview() 133 | } 134 | overlay.snp.makeConstraints { (make) in 135 | make.edges.equalToSuperview() 136 | } 137 | 138 | durationLabel.snp.makeConstraints { (make) in 139 | make.height.equalTo(durationLabel.font.pointSize + 10) 140 | make.leading.equalToSuperview().offset(8) 141 | make.trailing.equalToSuperview().inset(8) 142 | make.bottom.equalToSuperview() 143 | } 144 | } 145 | } 146 | 147 | class CustomAssetCellController: CommonExampleController { 148 | 149 | override func pressedPick(_ sender: Any) { 150 | 151 | let pickerConfig = AssetsPickerConfig() 152 | pickerConfig.assetCellType = CustomAssetCell.classForCoder() 153 | pickerConfig.assetPortraitColumnCount = 3 154 | pickerConfig.assetLandscapeColumnCount = 5 155 | pickerConfig.assetsMaximumSelectionCount = 10 156 | let picker = AssetsPickerViewController() 157 | picker.pickerConfig = pickerConfig 158 | picker.pickerDelegate = self 159 | 160 | present(picker, animated: true, completion: nil) 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/HideEmptyAlbumsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HideEmptyAlbumsController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 5/31/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import AssetsPickerViewController 10 | 11 | class HideEmptyAlbumsController: CommonExampleController { 12 | 13 | override func pressedPick(_ sender: Any) { 14 | 15 | let pickerConfig = AssetsPickerConfig() 16 | pickerConfig.albumIsShowEmptyAlbum = false 17 | 18 | let picker = AssetsPickerViewController() 19 | picker.pickerConfig = pickerConfig 20 | picker.pickerDelegate = self 21 | present(picker, animated: true, completion: nil) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/PreSelectedAssetsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreSelectedAssetsController.swift 3 | // AssetsPickerViewController_Example 4 | // 5 | // Created by DragonCherry on 9/27/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import AssetsPickerViewController 10 | 11 | class PreSelectedAssetsController: CommonExampleController { 12 | 13 | override func pressedPick(_ sender: Any) { 14 | 15 | let pickerConfig = AssetsPickerConfig() 16 | 17 | // set previously selected assets as selected assets on initial load 18 | pickerConfig.selectedAssets = self.assets 19 | 20 | let picker = AssetsPickerViewController() 21 | picker.pickerConfig = pickerConfig 22 | picker.pickerDelegate = self 23 | 24 | present(picker, animated: true, completion: nil) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/ShowAlbumListOnStartupController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShowAlbumListOnStartupController.swift 3 | // AssetsPickerViewController_Example 4 | // 5 | // Created by DragonCherry on 20/12/2017. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import AssetsPickerViewController 10 | 11 | class ShowAlbumListOnStartupController: CommonExampleController { 12 | 13 | override func pressedPick(_ sender: Any) { 14 | 15 | // I've tried to solve this inside library, but I found that it's hard to present one more VC in presenting VC. 16 | // So, this is kind of workaround that allows user to select album before see their photos. 17 | 18 | let pickerConfig = AssetsPickerConfig() 19 | 20 | let picker = AssetsPickerViewController() 21 | picker.pickerConfig = pickerConfig 22 | picker.pickerDelegate = self 23 | 24 | let albumVC = AssetsAlbumViewController(pickerConfig: pickerConfig) 25 | albumVC.delegate = picker.photoViewController 26 | let albumNavi = UINavigationController(rootViewController: albumVC) 27 | albumNavi.modalPresentationStyle = .overCurrentContext 28 | 29 | if #available(iOS 11.0, *) { 30 | albumNavi.navigationBar.prefersLargeTitles = true 31 | } 32 | 33 | picker.modalPresentationStyle = .overCurrentContext 34 | present(picker, animated: false, completion: nil) 35 | picker.view.isHidden = true 36 | picker.present(albumNavi, animated: true, completion: { 37 | picker.view.isHidden = false 38 | }) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ExampleUsages/ShowHiddenAlbumController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShowHiddenAlbumController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 6/1/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import AssetsPickerViewController 10 | 11 | class ShowHiddenAlbumController: CommonExampleController { 12 | 13 | override func pressedPick(_ sender: Any) { 14 | 15 | let pickerConfig = AssetsPickerConfig() 16 | pickerConfig.albumIsShowHiddenAlbum = true 17 | 18 | let picker = AssetsPickerViewController() 19 | picker.pickerConfig = pickerConfig 20 | picker.pickerDelegate = self 21 | present(picker, animated: true, completion: nil) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/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" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSCameraUsageDescription 31 | I need your approval! 32 | NSMicrophoneUsageDescription 33 | I need your approval! 34 | NSPhotoLibraryUsageDescription 35 | I need your approval! 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | UIInterfaceOrientationPortraitUpsideDown 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AssetsPickerViewController 4 | // 5 | // Created by DragonCherry on 05/17/2017. 6 | // Copyright (c) 2017 DragonCherry. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MainViewController: UITableViewController { 12 | 13 | let kCellReuseIdentifier: String = UUID().uuidString 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | tableView.register(UITableViewCell.classForCoder(), forCellReuseIdentifier: kCellReuseIdentifier) 18 | tableView.tableFooterView = UIView() 19 | } 20 | 21 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 22 | tableView.deselectRow(at: indexPath, animated: true) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ar.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ar.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/de.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/de.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/es-MX.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/es-MX.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/es.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/es.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/fr.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/fr.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/id.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/id.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/it.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/it.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ja.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ja.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ko.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ko.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ru.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/ru.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/tr.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/tr.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/zh-Hans.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/zh-Hans.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/zh-Hant.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 CocoaPods. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 CocoaPods. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "AssetsPickerViewController"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "AssetsPickerViewController"; 7 | -------------------------------------------------------------------------------- /Example/AssetsPickerViewController/zh-Hant.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITableViewSection"; headerTitle = "Basics"; ObjectID = "0UP-No-gFa"; */ 3 | "0UP-No-gFa.headerTitle" = "Basics"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "Filtering"; ObjectID = "4sM-rh-exU"; */ 6 | "4sM-rh-exU.headerTitle" = "Filtering"; 7 | 8 | /* Class = "UILabel"; text = "Basic Usage"; ObjectID = "DSZ-fY-z3N"; */ 9 | "DSZ-fY-z3N.text" = "Basic Usage"; 10 | 11 | /* Class = "UINavigationItem"; title = "Examples"; ObjectID = "JaI-xQ-Do9"; */ 12 | "JaI-xQ-Do9.title" = "Examples"; 13 | 14 | /* Class = "UITableViewSection"; headerTitle = "Appearance"; ObjectID = "JiI-DQ-t3T"; */ 15 | "JiI-DQ-t3T.headerTitle" = "Appearance"; 16 | 17 | /* Class = "UILabel"; text = "Custom Asset Order by PHFetchOptions"; ObjectID = "LNS-Of-WlE"; */ 18 | "LNS-Of-WlE.text" = "Custom Asset Order by PHFetchOptions"; 19 | 20 | /* Class = "UILabel"; text = "Custom Album Order by PHFetchOptions"; ObjectID = "TjC-o1-rUV"; */ 21 | "TjC-o1-rUV.text" = "Custom Album Order by PHFetchOptions"; 22 | 23 | /* Class = "UILabel"; text = "Custom Album Cell & Layout"; ObjectID = "V01-fT-RbR"; */ 24 | "V01-fT-RbR.text" = "Custom Album Cell & Layout"; 25 | 26 | /* Class = "UILabel"; text = "Show/Hide Empty Albums"; ObjectID = "ZnK-wU-zl5"; */ 27 | "ZnK-wU-zl5.text" = "Show/Hide Empty Albums"; 28 | 29 | /* Class = "UILabel"; text = "Custom Asset Cell & Layout"; ObjectID = "bFM-04-mtq"; */ 30 | "bFM-04-mtq.text" = "Custom Asset Cell & Layout"; 31 | 32 | /* Class = "UILabel"; text = "Custom Asset Filter by PHFetchOptions"; ObjectID = "fd7-wl-qNV"; */ 33 | "fd7-wl-qNV.text" = "Custom Asset Filter by PHFetchOptions"; 34 | 35 | /* Class = "UILabel"; text = "Custom Album Filter by PHFetchOptions"; ObjectID = "gbd-aR-wJN"; */ 36 | "gbd-aR-wJN.text" = "Custom Album Filter by PHFetchOptions"; 37 | 38 | /* Class = "UILabel"; text = "Custom Album Filter by Block"; ObjectID = "kN3-jP-3pt"; */ 39 | "kN3-jP-3pt.text" = "Custom Album Filter by Block"; 40 | 41 | /* Class = "UILabel"; text = "Custom Album Order by Block"; ObjectID = "kgI-yV-i1T"; */ 42 | "kgI-yV-i1T.text" = "Custom Album Order by Block"; 43 | 44 | /* Class = "UITableViewSection"; headerTitle = "Sorting"; ObjectID = "mxV-kP-Klc"; */ 45 | "mxV-kP-Klc.headerTitle" = "Sorting"; 46 | 47 | /* Class = "UILabel"; text = "Show/Hide Hidden Album"; ObjectID = "pp8-mo-h2x"; */ 48 | "pp8-mo-h2x.text" = "Show/Hide Hidden Album"; 49 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | platform :ios, '10.0' 3 | 4 | target 'AssetsPickerViewController_Example' do 5 | pod 'AssetsPickerViewController', :path => '../' 6 | end 7 | 8 | post_install do |installer| 9 | installer.pods_project.targets.each do |target| 10 | if target.name == 'TinyLog' 11 | target.build_configurations.each do |config| 12 | if config.name == 'Debug' 13 | config.build_settings['OTHER_SWIFT_FLAGS'] = '-D' 'DEBUG' 14 | else 15 | config.build_settings['OTHER_SWIFT_FLAGS'] = '' 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AssetsPickerViewController (2.9.6): 3 | - SnapKit 4 | - SnapKit (5.0.1) 5 | 6 | DEPENDENCIES: 7 | - AssetsPickerViewController (from `../`) 8 | 9 | SPEC REPOS: 10 | trunk: 11 | - SnapKit 12 | 13 | EXTERNAL SOURCES: 14 | AssetsPickerViewController: 15 | :path: "../" 16 | 17 | SPEC CHECKSUMS: 18 | AssetsPickerViewController: afad53707ec66ddd15182fa62630448736ed3976 19 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb 20 | 21 | PODFILE CHECKSUM: ed535a80c7e9986ea173935163bbd69a1c717311 22 | 23 | COCOAPODS: 1.9.3 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 DragonCherry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "AssetsPickerViewController", 6 | defaultLocalization: "en", 7 | platforms: [.iOS(.v10)], 8 | products: [ 9 | .library( 10 | name: "AssetsPickerViewController", 11 | targets: ["AssetsPickerViewController"] 12 | ) 13 | ], 14 | dependencies: [ 15 | .package(url: "https://github.com/SnapKit/SnapKit", from: "5.0.1") 16 | ], 17 | targets: [ 18 | .target( 19 | name: "AssetsPickerViewController", 20 | dependencies: ["SnapKit"], 21 | path: "AssetsPickerViewController", 22 | sources: ["Classes"], 23 | resources: [.process("Assets")] 24 | ) 25 | ], 26 | swiftLanguageVersions: [.v4_2, .v5] 27 | ) 28 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------