├── .gitignore ├── .swift-version ├── .travis.yml ├── Example ├── Podfile ├── Podfile.lock ├── Pods │ ├── Local Podspecs │ │ └── RAImagePicker.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ └── project.pbxproj │ └── Target Support Files │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests │ │ ├── Info.plist │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests-acknowledgements.markdown │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests-acknowledgements.plist │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests-dummy.m │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests-frameworks.sh │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests-resources.sh │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests-umbrella.h │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests.debug.xcconfig │ │ ├── Pods-RAImagePicker_Example-RAImagePicker_Tests.modulemap │ │ └── Pods-RAImagePicker_Example-RAImagePicker_Tests.release.xcconfig │ │ ├── Pods-RAImagePicker_Example │ │ ├── Info.plist │ │ ├── Pods-RAImagePicker_Example-acknowledgements.markdown │ │ ├── Pods-RAImagePicker_Example-acknowledgements.plist │ │ ├── Pods-RAImagePicker_Example-dummy.m │ │ ├── Pods-RAImagePicker_Example-frameworks.sh │ │ ├── Pods-RAImagePicker_Example-resources.sh │ │ ├── Pods-RAImagePicker_Example-umbrella.h │ │ ├── Pods-RAImagePicker_Example.debug.xcconfig │ │ ├── Pods-RAImagePicker_Example.modulemap │ │ └── Pods-RAImagePicker_Example.release.xcconfig │ │ └── RAImagePicker │ │ ├── Info.plist │ │ ├── RAImagePicker-dummy.m │ │ ├── RAImagePicker-prefix.pch │ │ ├── RAImagePicker-umbrella.h │ │ ├── RAImagePicker.modulemap │ │ ├── RAImagePicker.xcconfig │ │ └── ResourceBundle-RAImagePicker-Info.plist ├── RAImagePicker.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── RAImagePicker-Example.xcscheme ├── RAImagePicker.xcworkspace │ └── contents.xcworkspacedata ├── RAImagePicker │ ├── ActionItemCell.swift │ ├── ActionItemCell.xib │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── GalleryViewCell.swift │ ├── GalleryViewCell.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── camera.imageset │ │ │ ├── Contents.json │ │ │ └── camera.png │ │ ├── depth.imageset │ │ │ ├── Contents.json │ │ │ └── depth.png │ │ ├── gallery.imageset │ │ │ ├── Contents.json │ │ │ └── gallery.png │ │ ├── live.imageset │ │ │ ├── Contents.json │ │ │ └── live.png │ │ └── panorama.imageset │ │ │ ├── Contents.json │ │ │ └── panorama.png │ ├── Info.plist │ ├── VideosViewCell.swift │ ├── VideosViewCell.xib │ └── ViewController.swift └── Tests │ ├── Info.plist │ └── Tests.swift ├── LICENSE ├── RAImagePicker.podspec ├── RAImagePicker ├── Assets │ ├── .gitkeep │ └── Media.xcassets │ │ ├── Contents.json │ │ ├── background-rounded.imageset │ │ ├── Contents.json │ │ └── background-rounded.pdf │ │ ├── gradient.imageset │ │ ├── Contents.json │ │ ├── gradient.png │ │ ├── gradient@2x.png │ │ └── gradient@3x.png │ │ ├── icon-badge-livephoto.imageset │ │ ├── Contents.json │ │ └── icon-badge-livephoto.pdf │ │ ├── icon-badge-video.imageset │ │ ├── Contents.json │ │ └── icon-badge-video.pdf │ │ ├── icon-camera.imageset │ │ ├── Contents.json │ │ ├── icon-camera.png │ │ ├── icon-camera@2x.png │ │ └── icon-camera@3x.png │ │ ├── icon-check-background.imageset │ │ ├── Contents.json │ │ └── icon-ckeck-background.pdf │ │ ├── icon-check.imageset │ │ ├── Contents.json │ │ └── icon-check.pdf │ │ ├── icon-flip-camera.imageset │ │ ├── Contents.json │ │ └── flipCamera.pdf │ │ ├── icon-gallery.imageset │ │ ├── Contents.json │ │ ├── icon-gallery.png │ │ ├── icon-gallery@2x.png │ │ └── icon-gallery@3x.png │ │ ├── icon-live-off.imageset │ │ ├── Contents.json │ │ └── icon-live-off.pdf │ │ └── icon-live-on.imageset │ │ ├── Contents.json │ │ └── icon-live-on.pdf └── Classes │ ├── .gitkeep │ ├── Media │ ├── Camera │ │ └── RAPhotoCaptureDelegate.swift │ ├── RACaptureSession.swift │ └── Video │ │ ├── RAAVPreviewView.swift │ │ ├── RAVideoCaptureDelegate.swift │ │ └── RAVideoDataOuptutSampleBufferDelegate.swift │ ├── Processing │ ├── RAImagePicker-Bridging-Header.h │ ├── RAImagePickerAssetModel.swift │ ├── RAImagePickerDataSource.swift │ ├── RAImagePickerDelegate.swift │ ├── RAImagePickerLayout.swift │ ├── RAImagePickerSelectionPolicy.swift │ ├── RALayoutModel.swift │ ├── UIImageEffects.h │ └── UIImageEffects.m │ ├── Public │ ├── Layout │ │ ├── RAAppearance.swift │ │ ├── RACameraCollectionViewCell.swift │ │ ├── RACellRegistrator.swift │ │ └── RALayoutConfiguration.swift │ ├── RACaptureSettings.swift │ └── RAImagePickerController.swift │ └── Views │ ├── Cells │ ├── RAActionCell.swift │ ├── RAActionCell.xib │ ├── RAAssetCell.swift │ ├── RAImagePickerView.swift │ ├── RAImagePickerView.xib │ ├── RALivePhotoCameraCell.swift │ ├── RALivePhotoCameraCell.xib │ ├── RAVideoCameraCell.swift │ └── RAVideoCameraCell.xib │ ├── RACarvedLabel.swift │ ├── RARecordButton.swift │ ├── RARecordDurationLabel.swift │ ├── RAShutterButton.swift │ └── RAStationaryButton.swift ├── README.md └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | # Pods/ 34 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /.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: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/RAImagePicker.xcworkspace -scheme RAImagePicker-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'RAImagePicker_Example' do 4 | pod 'RAImagePicker', :path => '../' 5 | 6 | target 'RAImagePicker_Tests' do 7 | 8 | pod 'RAImagePicker', :path => '../' 9 | end 10 | end 11 | 12 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - RAImagePicker (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - RAImagePicker (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | RAImagePicker: 9 | :path: ../ 10 | 11 | SPEC CHECKSUMS: 12 | RAImagePicker: fd260a8299008bb122eebaae1ce3cba12854637a 13 | 14 | PODFILE CHECKSUM: 754044b7d178675e6b62794ca3c054387173c240 15 | 16 | COCOAPODS: 1.3.1 17 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/RAImagePicker.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RAImagePicker", 3 | "version": "0.1.0", 4 | "summary": "iMessage-like image picker for iOS +10.0", 5 | "description": "RAImagePicker is a protocol-oriented framework that provides custom features from the built-in Image Picker Edit.", 6 | "homepage": "https://github.com/rallahaseh/RAImagePicker", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "rallahaseh": "rallahaseh@gmail.com" 13 | }, 14 | "source": { 15 | "git": "https://github.com/rallahaseh/RAImagePicker.git", 16 | "tag": "0.1.0" 17 | }, 18 | "social_media_url": "https://twitter.com/rallahaseh", 19 | "platforms": { 20 | "ios": "10.0" 21 | }, 22 | "pod_target_xcconfig": { 23 | "SWIFT_VERSION": "4.0" 24 | }, 25 | "source_files": "RAImagePicker/Classes/**/*", 26 | "resources": [ 27 | "RAImagePicker/Assets/*" 28 | ], 29 | "frameworks": [ 30 | "UIKit", 31 | "Photos", 32 | "Foundation", 33 | "AVFoundation" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - RAImagePicker (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - RAImagePicker (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | RAImagePicker: 9 | :path: ../ 10 | 11 | SPEC CHECKSUMS: 12 | RAImagePicker: fd260a8299008bb122eebaae1ce3cba12854637a 13 | 14 | PODFILE CHECKSUM: 754044b7d178675e6b62794ca3c054387173c240 15 | 16 | COCOAPODS: 1.3.1 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## RAImagePicker 5 | 6 | Copyright (c) 2017 rallahaseh 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | Generated by CocoaPods - https://cocoapods.org 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2017 rallahaseh <rallahaseh@gmail.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | RAImagePicker 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Generated by CocoaPods - https://cocoapods.org 47 | Title 48 | 49 | Type 50 | PSGroupSpecifier 51 | 52 | 53 | StringsTable 54 | Acknowledgements 55 | Title 56 | Acknowledgements 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_RAImagePicker_Example_RAImagePicker_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_RAImagePicker_Example_RAImagePicker_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 10 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 11 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 12 | 13 | install_framework() 14 | { 15 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 16 | local source="${BUILT_PRODUCTS_DIR}/$1" 17 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 18 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 19 | elif [ -r "$1" ]; then 20 | local source="$1" 21 | fi 22 | 23 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 24 | 25 | if [ -L "${source}" ]; then 26 | echo "Symlinked..." 27 | source="$(readlink "${source}")" 28 | fi 29 | 30 | # Use filter instead of exclude so missing patterns don't throw errors. 31 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 32 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 33 | 34 | local basename 35 | basename="$(basename -s .framework "$1")" 36 | binary="${destination}/${basename}.framework/${basename}" 37 | if ! [ -r "$binary" ]; then 38 | binary="${destination}/${basename}" 39 | fi 40 | 41 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 42 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 43 | strip_invalid_archs "$binary" 44 | fi 45 | 46 | # Resign the code if required by the build settings to avoid unstable apps 47 | code_sign_if_enabled "${destination}/$(basename "$1")" 48 | 49 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 50 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 51 | local swift_runtime_libs 52 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 53 | for lib in $swift_runtime_libs; do 54 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 55 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 56 | code_sign_if_enabled "${destination}/${lib}" 57 | done 58 | fi 59 | } 60 | 61 | # Copies the dSYM of a vendored framework 62 | install_dsym() { 63 | local source="$1" 64 | if [ -r "$source" ]; then 65 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\"" 66 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}" 67 | fi 68 | } 69 | 70 | # Signs a framework with the provided identity 71 | code_sign_if_enabled() { 72 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 73 | # Use the current code_sign_identitiy 74 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 75 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 76 | 77 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 78 | code_sign_cmd="$code_sign_cmd &" 79 | fi 80 | echo "$code_sign_cmd" 81 | eval "$code_sign_cmd" 82 | fi 83 | } 84 | 85 | # Strip invalid architectures 86 | strip_invalid_archs() { 87 | binary="$1" 88 | # Get architectures for current file 89 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 90 | stripped="" 91 | for arch in $archs; do 92 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 93 | # Strip non-valid architectures in-place 94 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 95 | stripped="$stripped $arch" 96 | fi 97 | done 98 | if [[ "$stripped" ]]; then 99 | echo "Stripped $binary of architectures:$stripped" 100 | fi 101 | } 102 | 103 | 104 | if [[ "$CONFIGURATION" == "Debug" ]]; then 105 | install_framework "${BUILT_PRODUCTS_DIR}/RAImagePicker/RAImagePicker.framework" 106 | fi 107 | if [[ "$CONFIGURATION" == "Release" ]]; then 108 | install_framework "${BUILT_PRODUCTS_DIR}/RAImagePicker/RAImagePicker.framework" 109 | fi 110 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 111 | wait 112 | fi 113 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 14 | 15 | case "${TARGETED_DEVICE_FAMILY}" in 16 | 1,2) 17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 18 | ;; 19 | 1) 20 | TARGET_DEVICE_ARGS="--target-device iphone" 21 | ;; 22 | 2) 23 | TARGET_DEVICE_ARGS="--target-device ipad" 24 | ;; 25 | 3) 26 | TARGET_DEVICE_ARGS="--target-device tv" 27 | ;; 28 | 4) 29 | TARGET_DEVICE_ARGS="--target-device watch" 30 | ;; 31 | *) 32 | TARGET_DEVICE_ARGS="--target-device mac" 33 | ;; 34 | esac 35 | 36 | install_resource() 37 | { 38 | if [[ "$1" = /* ]] ; then 39 | RESOURCE_PATH="$1" 40 | else 41 | RESOURCE_PATH="${PODS_ROOT}/$1" 42 | fi 43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 44 | cat << EOM 45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 46 | EOM 47 | exit 1 48 | fi 49 | case $RESOURCE_PATH in 50 | *.storyboard) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.xib) 55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 57 | ;; 58 | *.framework) 59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 63 | ;; 64 | *.xcdatamodel) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 67 | ;; 68 | *.xcdatamodeld) 69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 71 | ;; 72 | *.xcmappingmodel) 73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 75 | ;; 76 | *.xcassets) 77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 79 | ;; 80 | *) 81 | echo "$RESOURCE_PATH" || true 82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 83 | ;; 84 | esac 85 | } 86 | 87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 92 | fi 93 | rm -f "$RESOURCES_TO_COPY" 94 | 95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 96 | then 97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 99 | while read line; do 100 | if [[ $line != "${PODS_ROOT}*" ]]; then 101 | XCASSET_FILES+=("$line") 102 | fi 103 | done <<<"$OTHER_XCASSETS" 104 | 105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 106 | fi 107 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_RAImagePicker_Example_RAImagePicker_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_RAImagePicker_Example_RAImagePicker_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/RAImagePicker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/RAImagePicker/RAImagePicker.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "RAImagePicker" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_RAImagePicker_Example_RAImagePicker_Tests { 2 | umbrella header "Pods-RAImagePicker_Example-RAImagePicker_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example-RAImagePicker_Tests/Pods-RAImagePicker_Example-RAImagePicker_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/RAImagePicker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/RAImagePicker/RAImagePicker.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "RAImagePicker" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## RAImagePicker 5 | 6 | Copyright (c) 2017 rallahaseh 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | Generated by CocoaPods - https://cocoapods.org 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2017 rallahaseh <rallahaseh@gmail.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | RAImagePicker 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Generated by CocoaPods - https://cocoapods.org 47 | Title 48 | 49 | Type 50 | PSGroupSpecifier 51 | 52 | 53 | StringsTable 54 | Acknowledgements 55 | Title 56 | Acknowledgements 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_RAImagePicker_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_RAImagePicker_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 10 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 11 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 12 | 13 | install_framework() 14 | { 15 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 16 | local source="${BUILT_PRODUCTS_DIR}/$1" 17 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 18 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 19 | elif [ -r "$1" ]; then 20 | local source="$1" 21 | fi 22 | 23 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 24 | 25 | if [ -L "${source}" ]; then 26 | echo "Symlinked..." 27 | source="$(readlink "${source}")" 28 | fi 29 | 30 | # Use filter instead of exclude so missing patterns don't throw errors. 31 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 32 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 33 | 34 | local basename 35 | basename="$(basename -s .framework "$1")" 36 | binary="${destination}/${basename}.framework/${basename}" 37 | if ! [ -r "$binary" ]; then 38 | binary="${destination}/${basename}" 39 | fi 40 | 41 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 42 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 43 | strip_invalid_archs "$binary" 44 | fi 45 | 46 | # Resign the code if required by the build settings to avoid unstable apps 47 | code_sign_if_enabled "${destination}/$(basename "$1")" 48 | 49 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 50 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 51 | local swift_runtime_libs 52 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 53 | for lib in $swift_runtime_libs; do 54 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 55 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 56 | code_sign_if_enabled "${destination}/${lib}" 57 | done 58 | fi 59 | } 60 | 61 | # Copies the dSYM of a vendored framework 62 | install_dsym() { 63 | local source="$1" 64 | if [ -r "$source" ]; then 65 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\"" 66 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}" 67 | fi 68 | } 69 | 70 | # Signs a framework with the provided identity 71 | code_sign_if_enabled() { 72 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 73 | # Use the current code_sign_identitiy 74 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 75 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 76 | 77 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 78 | code_sign_cmd="$code_sign_cmd &" 79 | fi 80 | echo "$code_sign_cmd" 81 | eval "$code_sign_cmd" 82 | fi 83 | } 84 | 85 | # Strip invalid architectures 86 | strip_invalid_archs() { 87 | binary="$1" 88 | # Get architectures for current file 89 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 90 | stripped="" 91 | for arch in $archs; do 92 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 93 | # Strip non-valid architectures in-place 94 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 95 | stripped="$stripped $arch" 96 | fi 97 | done 98 | if [[ "$stripped" ]]; then 99 | echo "Stripped $binary of architectures:$stripped" 100 | fi 101 | } 102 | 103 | 104 | if [[ "$CONFIGURATION" == "Debug" ]]; then 105 | install_framework "${BUILT_PRODUCTS_DIR}/RAImagePicker/RAImagePicker.framework" 106 | fi 107 | if [[ "$CONFIGURATION" == "Release" ]]; then 108 | install_framework "${BUILT_PRODUCTS_DIR}/RAImagePicker/RAImagePicker.framework" 109 | fi 110 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 111 | wait 112 | fi 113 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 14 | 15 | case "${TARGETED_DEVICE_FAMILY}" in 16 | 1,2) 17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 18 | ;; 19 | 1) 20 | TARGET_DEVICE_ARGS="--target-device iphone" 21 | ;; 22 | 2) 23 | TARGET_DEVICE_ARGS="--target-device ipad" 24 | ;; 25 | 3) 26 | TARGET_DEVICE_ARGS="--target-device tv" 27 | ;; 28 | 4) 29 | TARGET_DEVICE_ARGS="--target-device watch" 30 | ;; 31 | *) 32 | TARGET_DEVICE_ARGS="--target-device mac" 33 | ;; 34 | esac 35 | 36 | install_resource() 37 | { 38 | if [[ "$1" = /* ]] ; then 39 | RESOURCE_PATH="$1" 40 | else 41 | RESOURCE_PATH="${PODS_ROOT}/$1" 42 | fi 43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 44 | cat << EOM 45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 46 | EOM 47 | exit 1 48 | fi 49 | case $RESOURCE_PATH in 50 | *.storyboard) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.xib) 55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 57 | ;; 58 | *.framework) 59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 63 | ;; 64 | *.xcdatamodel) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 67 | ;; 68 | *.xcdatamodeld) 69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 71 | ;; 72 | *.xcmappingmodel) 73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 75 | ;; 76 | *.xcassets) 77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 79 | ;; 80 | *) 81 | echo "$RESOURCE_PATH" || true 82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 83 | ;; 84 | esac 85 | } 86 | 87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 92 | fi 93 | rm -f "$RESOURCES_TO_COPY" 94 | 95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 96 | then 97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 99 | while read line; do 100 | if [[ $line != "${PODS_ROOT}*" ]]; then 101 | XCASSET_FILES+=("$line") 102 | fi 103 | done <<<"$OTHER_XCASSETS" 104 | 105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 106 | fi 107 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_RAImagePicker_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_RAImagePicker_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/RAImagePicker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/RAImagePicker/RAImagePicker.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "RAImagePicker" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_RAImagePicker_Example { 2 | umbrella header "Pods-RAImagePicker_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-RAImagePicker_Example/Pods-RAImagePicker_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/RAImagePicker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/RAImagePicker/RAImagePicker.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "RAImagePicker" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/RAImagePicker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/RAImagePicker/RAImagePicker-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_RAImagePicker : NSObject 3 | @end 4 | @implementation PodsDummy_RAImagePicker 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/RAImagePicker/RAImagePicker-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/RAImagePicker/RAImagePicker-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #import "RAImagePicker-Bridging-Header.h" 14 | #import "UIImageEffects.h" 15 | 16 | FOUNDATION_EXPORT double RAImagePickerVersionNumber; 17 | FOUNDATION_EXPORT const unsigned char RAImagePickerVersionString[]; 18 | 19 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/RAImagePicker/RAImagePicker.modulemap: -------------------------------------------------------------------------------- 1 | framework module RAImagePicker { 2 | umbrella header "RAImagePicker-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/RAImagePicker/RAImagePicker.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/RAImagePicker 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_LDFLAGS = -framework "AVFoundation" -framework "Foundation" -framework "Photos" -framework "UIKit" 5 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 6 | PODS_BUILD_DIR = $BUILD_DIR 7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | SWIFT_VERSION = 4.0 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/RAImagePicker/ResourceBundle-RAImagePicker-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 0.1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/RAImagePicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RAImagePicker.xcodeproj/xcshareddata/xcschemes/RAImagePicker-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 101 | 107 | 108 | 109 | 110 | 112 | 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /Example/RAImagePicker.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/RAImagePicker/ActionItemCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionItemCell.swift 3 | // RAImagePicker_Example 4 | // 5 | // Created by Rashed Al Lahaseh on 12/5/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ActionItemCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var title: UILabel! 14 | @IBOutlet weak var icon: UIImageView! 15 | @IBOutlet weak var topOffset: NSLayoutConstraint! 16 | @IBOutlet weak var bottomOffset: NSLayoutConstraint! 17 | 18 | private var originalBC: UIColor? 19 | 20 | override func awakeFromNib() { 21 | super.awakeFromNib() 22 | // Initialization code 23 | 24 | originalBC = backgroundColor 25 | icon.backgroundColor = UIColor.clear 26 | } 27 | 28 | override var isHighlighted: Bool { 29 | didSet { 30 | if isHighlighted { 31 | backgroundColor = UIColor.red 32 | } 33 | else { 34 | backgroundColor = originalBC 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/RAImagePicker/ActionItemCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Example/RAImagePicker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RAImagePicker 4 | // 5 | // Created by rallahaseh on 11/29/2017. 6 | // Copyright (c) 2017 rallahaseh. 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: [UIApplicationLaunchOptionsKey: 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/RAImagePicker/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/RAImagePicker/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /Example/RAImagePicker/GalleryViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GalleryViewCell.swift 3 | // RAImagePicker_Example 4 | // 5 | // Created by Rashed Al Lahaseh on 12/5/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | import RAImagePicker 12 | 13 | class GalleryViewCell: UICollectionViewCell, RAImagePickerAssetCell { 14 | 15 | @IBOutlet weak var imageView: UIImageView! 16 | 17 | @IBOutlet weak var subtypeImageView: UIImageView! 18 | @IBOutlet weak var selectedView: UIView! 19 | 20 | var representedAssetIdentifier: String? 21 | 22 | override func awakeFromNib() { 23 | super.awakeFromNib() 24 | // Initialization code 25 | 26 | subtypeImageView.backgroundColor = UIColor.clear 27 | selectedView.isHidden = !isSelected 28 | } 29 | 30 | override func prepareForReuse() { 31 | super.prepareForReuse() 32 | // Initialization code 33 | 34 | imageView.image = nil 35 | subtypeImageView.image = nil 36 | } 37 | 38 | override var isSelected: Bool { 39 | didSet { 40 | selectedView.isHidden = !isSelected 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Example/RAImagePicker/GalleryViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Example/RAImagePicker/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/RAImagePicker/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/camera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "camera.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/camera.imageset/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/Example/RAImagePicker/Images.xcassets/camera.imageset/camera.png -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/depth.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "depth.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/depth.imageset/depth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/Example/RAImagePicker/Images.xcassets/depth.imageset/depth.png -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/gallery.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "gallery.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/gallery.imageset/gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/Example/RAImagePicker/Images.xcassets/gallery.imageset/gallery.png -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/live.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "live.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/live.imageset/live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/Example/RAImagePicker/Images.xcassets/live.imageset/live.png -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/panorama.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "panorama.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/RAImagePicker/Images.xcassets/panorama.imageset/panorama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/Example/RAImagePicker/Images.xcassets/panorama.imageset/panorama.png -------------------------------------------------------------------------------- /Example/RAImagePicker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | NSCameraUsageDescription 39 | Need Access to Camera to Capture Photos and Record Videos 40 | NSMicrophoneUsageDescription 41 | Need Access to Microphone for Recording Videos 42 | NSPhotoLibraryUsageDescription 43 | Need Access to Photos for Picking Images 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/RAImagePicker/VideosViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideosViewCell.swift 3 | // RAImagePicker_Example 4 | // 5 | // Created by Rashed Al Lahaseh on 12/5/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class VideosViewCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var imageView: UIImageView! 14 | @IBOutlet weak var label: UILabel! 15 | 16 | var representedAssetIdentifier: String? 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | // Initialization code 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/RAImagePicker/VideosViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import XCTest 3 | import RAImagePicker 4 | 5 | class Tests: XCTestCase { 6 | 7 | override func setUp() { 8 | super.setUp() 9 | // Put setup code here. This method is called before the invocation of each test method in the class. 10 | } 11 | 12 | override func tearDown() { 13 | // Put teardown code here. This method is called after the invocation of each test method in the class. 14 | super.tearDown() 15 | } 16 | 17 | func testExample() { 18 | // This is an example of a functional test case. 19 | XCTAssert(true, "Pass") 20 | } 21 | 22 | func testPerformanceExample() { 23 | // This is an example of a performance test case. 24 | self.measure() { 25 | // Put the code you want to measure the time of here. 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 rallahaseh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /RAImagePicker.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint RAImagePicker.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 = 'RAImagePicker' 11 | s.version = '0.1.0' 12 | s.summary = 'iMessage-like image picker for iOS +10.0' 13 | s.description = <<-DESC 14 | RAImagePicker is a protocol-oriented framework that provides custom features from the built-in Image Picker Edit. 15 | DESC 16 | s.homepage = 'https://github.com/rallahaseh/RAImagePicker' 17 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 18 | s.license = { :type => 'MIT', :file => 'LICENSE' } 19 | s.author = { 'rallahaseh' => 'rallahaseh@gmail.com' } 20 | s.source = { :git => 'https://github.com/rallahaseh/RAImagePicker.git', :tag => s.version.to_s } 21 | s.social_media_url = 'https://twitter.com/rallahaseh' 22 | 23 | s.ios.deployment_target = '10.0' 24 | 25 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '4.0' } 26 | 27 | s.source_files = 'RAImagePicker/Classes/**/*' 28 | 29 | s.resources = ['RAImagePicker/Assets/*'] 30 | 31 | s.frameworks = 'UIKit', 'Photos', 'Foundation', 'AVFoundation' 32 | 33 | end 34 | -------------------------------------------------------------------------------- /RAImagePicker/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/.gitkeep -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/background-rounded.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "background-rounded.pdf", 6 | "resizing" : { 7 | "mode" : "9-part", 8 | "center" : { 9 | "mode" : "tile", 10 | "width" : 1, 11 | "height" : 1 12 | }, 13 | "cap-insets" : { 14 | "bottom" : 12, 15 | "top" : 12, 16 | "right" : 12, 17 | "left" : 12 18 | } 19 | } 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | }, 26 | "properties" : { 27 | "template-rendering-intent" : "template" 28 | } 29 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/background-rounded.imageset/background-rounded.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/background-rounded.imageset/background-rounded.pdf -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/gradient.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "gradient.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "gradient@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "gradient@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | }, 23 | "properties" : { 24 | "compression-type" : "gpu-optimized-smallest", 25 | "template-rendering-intent" : "original" 26 | } 27 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/gradient.imageset/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/gradient.imageset/gradient.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/gradient.imageset/gradient@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/gradient.imageset/gradient@2x.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/gradient.imageset/gradient@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/gradient.imageset/gradient@3x.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-badge-livephoto.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-badge-livephoto.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-badge-livephoto.imageset/icon-badge-livephoto.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-badge-livephoto.imageset/icon-badge-livephoto.pdf -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-badge-video.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-badge-video.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-badge-video.imageset/icon-badge-video.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-badge-video.imageset/icon-badge-video.pdf -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-camera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-camera.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-camera@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-camera@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-camera.imageset/icon-camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-camera.imageset/icon-camera.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-camera.imageset/icon-camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-camera.imageset/icon-camera@2x.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-camera.imageset/icon-camera@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-camera.imageset/icon-camera@3x.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-check-background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-ckeck-background.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-check-background.imageset/icon-ckeck-background.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-check-background.imageset/icon-ckeck-background.pdf -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-check.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-check.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-check.imageset/icon-check.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-check.imageset/icon-check.pdf -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-flip-camera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "flipCamera.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-flip-camera.imageset/flipCamera.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-flip-camera.imageset/flipCamera.pdf -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-gallery.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-gallery.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-gallery@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-gallery@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-gallery.imageset/icon-gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-gallery.imageset/icon-gallery.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-gallery.imageset/icon-gallery@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-gallery.imageset/icon-gallery@2x.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-gallery.imageset/icon-gallery@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-gallery.imageset/icon-gallery@3x.png -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-live-off.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-live-off.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-live-off.imageset/icon-live-off.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-live-off.imageset/icon-live-off.pdf -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-live-on.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-live-on.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /RAImagePicker/Assets/Media.xcassets/icon-live-on.imageset/icon-live-on.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Assets/Media.xcassets/icon-live-on.imageset/icon-live-on.pdf -------------------------------------------------------------------------------- /RAImagePicker/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rallahaseh/RAImagePicker/b6109e774856ba4c31408c1b68f24146f32e101b/RAImagePicker/Classes/.gitkeep -------------------------------------------------------------------------------- /RAImagePicker/Classes/Media/Camera/RAPhotoCaptureDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAPhotoCaptureDelegate.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 11/29/17. 6 | // 7 | 8 | import AVFoundation 9 | import Photos 10 | 11 | final class RAPhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate { 12 | 13 | // MARK: - Public Methods 14 | 15 | /// set this to false if you dont wish to save taken picture to photo library 16 | var savesPhotoToLibrary = true 17 | 18 | /// this contains photo data when taken 19 | private(set) var photoData: Data? = nil 20 | 21 | private(set) var requestedPhotoSettings: AVCapturePhotoSettings 22 | 23 | /// not nil if error occured during capturing 24 | private(set) var processError: Error? 25 | 26 | // MARK: Private Methods 27 | 28 | private let willCapturePhotoAnimation: () -> () 29 | private let capturingLivePhoto: (Bool) -> () 30 | private let completed: (RAPhotoCaptureDelegate) -> () 31 | private var livePhotoCompanionMovieURL: URL? = nil 32 | 33 | init(with requestedPhotoSettings: AVCapturePhotoSettings, willCapturePhotoAnimation: @escaping () -> (), capturingLivePhoto: @escaping (Bool) -> (), completed: @escaping (RAPhotoCaptureDelegate) -> ()) { 34 | self.requestedPhotoSettings = requestedPhotoSettings 35 | self.willCapturePhotoAnimation = willCapturePhotoAnimation 36 | self.capturingLivePhoto = capturingLivePhoto 37 | self.completed = completed 38 | } 39 | 40 | private func didFinish() { 41 | if let livePhotoCompanionMoviePath = livePhotoCompanionMovieURL?.path { 42 | if FileManager.default.fileExists(atPath: livePhotoCompanionMoviePath) { 43 | do { 44 | try FileManager.default.removeItem(atPath: livePhotoCompanionMoviePath) 45 | } 46 | catch { 47 | print("photo capture delegate: Could not remove file at url: \(livePhotoCompanionMoviePath)") 48 | } 49 | } 50 | } 51 | 52 | completed(self) 53 | } 54 | 55 | func photoOutput(_ captureOutput: AVCapturePhotoOutput, willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) { 56 | if resolvedSettings.livePhotoMovieDimensions.width > 0 && resolvedSettings.livePhotoMovieDimensions.height > 0 { 57 | capturingLivePhoto(true) 58 | } 59 | } 60 | 61 | func photoOutput(_ captureOutput: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) { 62 | willCapturePhotoAnimation() 63 | } 64 | 65 | @available(iOS 10.0, *) 66 | func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { 67 | if let photoSampleBuffer = photoSampleBuffer { 68 | photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer) 69 | } 70 | else if let error = error { 71 | print("photo capture delegate: error capturing photo: \(error)") 72 | processError = error 73 | return 74 | } 75 | } 76 | 77 | @available(iOS 10.0, *) 78 | func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishRecordingLivePhotoMovieForEventualFileAt outputFileURL: URL, resolvedSettings: AVCaptureResolvedPhotoSettings) { 79 | capturingLivePhoto(false) 80 | } 81 | 82 | @available(iOS 10.0, *) 83 | func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingLivePhotoToMovieFileAt outputFileURL: URL, duration: CMTime, photoDisplayTime: CMTime, resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { 84 | if let error = error { 85 | print("photo capture delegate: error processing live photo companion movie: \(error)") 86 | return 87 | } 88 | 89 | livePhotoCompanionMovieURL = outputFileURL 90 | } 91 | 92 | @available(iOS 10.0, *) 93 | func photoOutput(_ output: AVCapturePhotoOutput, didCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { 94 | 95 | if let error = error { 96 | print("photo capture delegate: Error capturing photo: \(error)") 97 | didFinish() 98 | return 99 | } 100 | 101 | guard let photoData = photoData else { 102 | print("photo capture delegate: No photo data resource") 103 | didFinish() 104 | return 105 | } 106 | 107 | guard savesPhotoToLibrary == true else { 108 | print("photo capture delegate: photo did finish without saving to photo library") 109 | didFinish() 110 | return 111 | } 112 | 113 | PHPhotoLibrary.requestAuthorization { [unowned self] status in 114 | if status == .authorized { 115 | PHPhotoLibrary.shared().performChanges({ [unowned self] in 116 | let creationRequest = PHAssetCreationRequest.forAsset() 117 | creationRequest.addResource(with: .photo, data: photoData, options: nil) 118 | 119 | if let livePhotoCompanionMovieURL = self.livePhotoCompanionMovieURL { 120 | let livePhotoCompanionMovieFileResourceOptions = PHAssetResourceCreationOptions() 121 | livePhotoCompanionMovieFileResourceOptions.shouldMoveFile = true 122 | creationRequest.addResource(with: .pairedVideo, fileURL: livePhotoCompanionMovieURL, options: livePhotoCompanionMovieFileResourceOptions) 123 | } 124 | 125 | }, completionHandler: { [unowned self] success, error in 126 | if let error = error { 127 | print("photo capture delegate: Error occurered while saving photo to photo library: \(error)") 128 | } 129 | self.didFinish() 130 | } 131 | ) 132 | } 133 | else { 134 | self.didFinish() 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Media/Video/RAAVPreviewView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAAVPreviewView.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 11/29/17. 6 | // 7 | 8 | import UIKit 9 | import Foundation 10 | import AVFoundation 11 | 12 | enum VideoDisplayMode { 13 | 14 | // Preserve aspect ration, fit within layer bounds 15 | case aspectFit 16 | // Preserve aspect ratio, fill view bounds 17 | case aspectFill 18 | // Stretch to fill layer bounds 19 | case resize 20 | } 21 | 22 | // View whose layer is AVCaptureVideoPreviewLayer to preview the output of a captured session 23 | final class AVPreviewView: UIView { 24 | 25 | // MARK: - Public Methods 26 | 27 | var previewLayer: AVCaptureVideoPreviewLayer { 28 | return layer as! AVCaptureVideoPreviewLayer 29 | } 30 | 31 | var session: AVCaptureSession? { 32 | get { 33 | return previewLayer.session 34 | } 35 | set { 36 | if previewLayer.session === newValue { 37 | return 38 | } 39 | previewLayer.session = newValue 40 | } 41 | } 42 | 43 | var displayMode: VideoDisplayMode = .aspectFill { 44 | didSet { 45 | applyVideoDisplayMode() 46 | } 47 | } 48 | 49 | override class var layerClass: AnyClass { 50 | return AVCaptureVideoPreviewLayer.self 51 | } 52 | 53 | override init(frame: CGRect) { 54 | super.init(frame: frame) 55 | applyVideoDisplayMode() 56 | } 57 | 58 | required init?(coder aDecoder: NSCoder) { 59 | super.init(coder: aDecoder) 60 | applyVideoDisplayMode() 61 | } 62 | 63 | // MARK: - Private Methods 64 | 65 | private func applyVideoDisplayMode() { 66 | switch displayMode { 67 | case .aspectFill: previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill 68 | case .aspectFit: previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect 69 | case .resize: previewLayer.videoGravity = AVLayerVideoGravity.resize 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Media/Video/RAVideoCaptureDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAVideoCaptureDelegate.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 11/29/17. 6 | // 7 | 8 | import AVFoundation 9 | import Photos 10 | 11 | final class RAVideoCaptureDelegate: NSObject, AVCaptureFileOutputRecordingDelegate { 12 | 13 | // MARK: - Public Methods 14 | 15 | // Save videos to library by default true 16 | var savesVideoToLibrary = true 17 | 18 | // Flag to detect when canceling recording(stope without saving) 19 | var isBeingCancelled = false 20 | 21 | // Flag to detect when system inturrupts recording due to various reasons (phone call, background, empty space, ...) 22 | var recordingWasInterrupted = false 23 | 24 | // Recording errors (nil if cancelled and none if failed or interrupted) 25 | private(set) var recordingError: Error? 26 | 27 | init(didStart: @escaping ()->(), didFinish: @escaping (RAVideoCaptureDelegate)->(), didFail: @escaping (RAVideoCaptureDelegate, Error)->()) { 28 | self.didStart = didStart 29 | self.didFinish = didFinish 30 | self.didFail = didFail 31 | 32 | if UIDevice.current.isMultitaskingSupported { 33 | /* 34 | Setup background task. 35 | - This is needed because the `capture(_:, didFinishRecordingToOutputFileAt:, fromConnections:, error:)` 36 | callback is not received until AVCam returns to the foreground unless you request background 37 | execution time. 38 | - This also ensures that there will be time to write the file to the photo library when 39 | AVCam is backgrounded. 40 | - To conclude this background execution, endBackgroundTask(_:) is called in 41 | `capture(_:, didFinishRecordingToOutputFileAt:, fromConnections:, error:)` 42 | after the recorded file has been saved. 43 | */ 44 | self.backgroundRecordingID = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) 45 | } 46 | } 47 | 48 | // MARK: - Private Methods 49 | 50 | private var backgroundRecordingID: UIBackgroundTaskIdentifier? = nil 51 | private var didStart: ()->() 52 | private var didFinish: (RAVideoCaptureDelegate)->() 53 | private var didFail: (RAVideoCaptureDelegate, Error)->() 54 | 55 | private func cleanUp(deleteFile: Bool, saveToAssets: Bool, outputFileURL: URL) { 56 | 57 | func deleteFileIfNeeded() { 58 | guard deleteFile == true else { return } 59 | let path = outputFileURL.path 60 | if FileManager.default.fileExists(atPath: path) { 61 | do { 62 | try FileManager.default.removeItem(atPath: path) 63 | } 64 | catch let error { 65 | print("capture session: could not remove recording at url: \(outputFileURL)") 66 | print("capture session: error: \(error)") 67 | } 68 | } 69 | } 70 | 71 | if let currentBackgroundRecordingID = backgroundRecordingID { 72 | backgroundRecordingID = UIBackgroundTaskInvalid 73 | if currentBackgroundRecordingID != UIBackgroundTaskInvalid { 74 | UIApplication.shared.endBackgroundTask(currentBackgroundRecordingID) 75 | } 76 | } 77 | 78 | if saveToAssets { 79 | PHPhotoLibrary.requestAuthorization { status in 80 | if status == .authorized { 81 | PHPhotoLibrary.shared().performChanges({ 82 | let creationRequest = PHAssetCreationRequest.forAsset() 83 | let videoResourceOptions = PHAssetResourceCreationOptions() 84 | videoResourceOptions.shouldMoveFile = true 85 | creationRequest.addResource(with: .video, fileURL: outputFileURL, options: videoResourceOptions) 86 | }, completionHandler: { success, error in 87 | if let error = error { 88 | print("capture session: Error occurered while saving video to photo library: \(error)") 89 | deleteFileIfNeeded() 90 | } 91 | }) 92 | } 93 | else { 94 | deleteFileIfNeeded() 95 | } 96 | } 97 | } 98 | else { 99 | deleteFileIfNeeded() 100 | } 101 | } 102 | 103 | // MARK: - AVCaptureFileOutputRecordingDelegate Methods 104 | 105 | func fileOutput(_ captureOutput: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) { 106 | didStart() 107 | } 108 | 109 | func fileOutput(_ captureOutput: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { 110 | 111 | if let error = error { 112 | 113 | recordingError = error 114 | 115 | print("capture session: movie recording failed error: \(error)") 116 | 117 | // Flag to check the video can be delivered even if the recording stopped due to a reason (no space, ..) 118 | let successfullyFinished = (((error as NSError).userInfo[AVErrorRecordingSuccessfullyFinishedKey] as AnyObject).boolValue) ?? false 119 | 120 | if successfullyFinished { 121 | recordingWasInterrupted = true 122 | cleanUp(deleteFile: true, saveToAssets: savesVideoToLibrary, outputFileURL: outputFileURL) 123 | didFail(self, error) 124 | } 125 | else { 126 | cleanUp(deleteFile: true, saveToAssets: false, outputFileURL: outputFileURL) 127 | didFail(self, error) 128 | } 129 | } 130 | else if isBeingCancelled == true { 131 | cleanUp(deleteFile: true, saveToAssets: false, outputFileURL: outputFileURL) 132 | didFinish(self) 133 | } 134 | else { 135 | cleanUp(deleteFile: true, saveToAssets: savesVideoToLibrary, outputFileURL: outputFileURL) 136 | didFinish(self) 137 | } 138 | } 139 | } 140 | 141 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Media/Video/RAVideoDataOuptutSampleBufferDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAVideoDataOuptutSampleBufferDelegate.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 11/29/17. 6 | // 7 | 8 | import Foundation 9 | import AVFoundation 10 | 11 | 12 | // Note: if video file output is provided, video data output is not working!!! there must be only 1 output at the same time 13 | final class RAVideoOutputSampleBufferDelegate : NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { 14 | 15 | let processQueue = DispatchQueue(label: "video-output-sample-buffer-delegate.queue") 16 | 17 | var latestImage: UIImage? { 18 | return latestSampleBuffer?.imageRepresentation 19 | } 20 | 21 | private var latestSampleBuffer: CMSampleBuffer? 22 | 23 | @nonobjc func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 24 | latestSampleBuffer = sampleBuffer 25 | } 26 | 27 | @nonobjc func captureOutput(captureOutput: AVCaptureFileOutput, didFinishRecordingToOutputFileAtURL outputFileURL: NSURL, fromConnections connections: [AnyObject], error: NSError!) { 28 | } 29 | } 30 | 31 | extension CMSampleBuffer { 32 | 33 | static let context = CIContext(options: [kCIContextUseSoftwareRenderer: false]) 34 | 35 | // Converts sample buffer to UIImage with backing CGImage 36 | fileprivate var imageRepresentation: UIImage? { 37 | 38 | guard let pixelBuffer = CMSampleBufferGetImageBuffer(self) else { 39 | return nil 40 | } 41 | 42 | let ciImage = CIImage(cvPixelBuffer: pixelBuffer) 43 | 44 | // Down Scale Image 45 | let filter = CIFilter(name: "CILanczosScaleTransform")! 46 | filter.setValue(ciImage, forKey: "inputImage") 47 | filter.setValue(0.25, forKey: "inputScale") 48 | filter.setValue(1.0, forKey: "inputAspectRatio") 49 | let resizedCiImage = filter.value(forKey: "outputImage") as! CIImage 50 | 51 | 52 | // We need to convert CIImage to CGImage because we are using Apples blurring functions 53 | // This conversion is very expensive, use it only when you really need it 54 | if let cgImage = CMSampleBuffer.context.createCGImage(resizedCiImage, from: resizedCiImage.extent) { 55 | return UIImage(cgImage: cgImage) 56 | } 57 | 58 | return nil 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Processing/RAImagePicker-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Header.h 3 | // Pods 4 | // 5 | // Created by Rashed Al Lahaseh on 12/6/17. 6 | // 7 | 8 | #import 9 | 10 | #import "UIImageEffects.h" 11 | 12 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Processing/RAImagePickerAssetModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAImagePickerAssetModel.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import Foundation 9 | import Photos 10 | 11 | 12 | // Model that is used when accessing an caching PHAsset objects 13 | final class RAImagePickerAssetModel { 14 | 15 | var fetchResult: PHFetchResult! { 16 | set { userDefinedFetchResult = newValue } 17 | get { return userDefinedFetchResult ?? defaultFetchResult } 18 | } 19 | 20 | lazy var imageManager = PHCachingImageManager() 21 | var thumbnailSize: CGSize? 22 | 23 | /* 24 | Tries to access smart album .smartAlbumUserLibrary that should be `Camera Roll` and uses just fetchAssets as fallback 25 | */ 26 | private lazy var defaultFetchResult: PHFetchResult = { 27 | 28 | let assetsOptions = PHFetchOptions() 29 | assetsOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] 30 | assetsOptions.fetchLimit = 1000 31 | 32 | let collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil) 33 | if let cameraRoll = collections.firstObject { 34 | return PHAsset.fetchAssets(in: cameraRoll, options: assetsOptions) 35 | } 36 | else { 37 | return PHAsset.fetchAssets(with: assetsOptions) 38 | } 39 | }() 40 | 41 | private var userDefinedFetchResult: PHFetchResult? 42 | 43 | //will be use for caching 44 | var previousPreheatRect = CGRect.zero 45 | 46 | func updateCachedAssets(collectionView: UICollectionView) { 47 | 48 | // Paradoxly, using this precaching the scrolling of images is more laggy than if there is no precaching 49 | 50 | guard let thumbnailSize = thumbnailSize else { 51 | return print("Asset Model: update cache assets - thumbnail size is nil") 52 | } 53 | 54 | guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { 55 | return print("Asset Model: update cache assets - collection view layout is not flow layout") 56 | } 57 | 58 | // The preheat window is twice the height of the visible rect. 59 | let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size) 60 | 61 | var preheatRect: CGRect 62 | 63 | switch layout.scrollDirection { 64 | case .vertical: 65 | 66 | preheatRect = visibleRect.insetBy(dx: 0, dy: -0.75 * visibleRect.height) 67 | 68 | // Update only if the visible area is significantly different from the last preheated area. 69 | let delta = abs(preheatRect.midY - previousPreheatRect.midY) 70 | guard delta > collectionView.bounds.height / 3 else { 71 | return 72 | } 73 | 74 | case .horizontal: 75 | 76 | preheatRect = visibleRect.insetBy(dx: -0.75 * visibleRect.width, dy: 0) 77 | 78 | // Update only if the visible area is significantly different from the last preheated area. 79 | let delta = abs(preheatRect.midX - previousPreheatRect.midX) 80 | guard delta > collectionView.bounds.width / 3 else { 81 | return 82 | } 83 | } 84 | 85 | // Compute the assets to start caching and to stop caching. 86 | let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect, layout.scrollDirection) 87 | let addedAssets = addedRects 88 | .flatMap { rect in collectionView.indexPathsForElements(in: rect) } 89 | .map { indexPath in fetchResult.object(at: indexPath.item) } 90 | let removedAssets = removedRects 91 | .flatMap { rect in collectionView.indexPathsForElements(in: rect) } 92 | .map { indexPath in fetchResult.object(at: indexPath.item) } 93 | 94 | // Update the assets the PHCachingImageManager is caching. 95 | imageManager.startCachingImages(for: addedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) 96 | debugPrint("Asset Model: caching, size \(thumbnailSize), preheat rect \(preheatRect), items \(addedAssets.count)") 97 | 98 | imageManager.stopCachingImages(for: removedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) 99 | debugPrint("Asset Model: uncaching, preheat rect \(preheatRect), items \(removedAssets.count)") 100 | 101 | // Store the preheat rect to compare against in the future. 102 | previousPreheatRect = preheatRect 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Processing/RAImagePickerDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAImagePickerDataSource.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import Foundation 9 | import Photos 10 | 11 | 12 | // Datasource for a collection view that is used by RAImagePickerViewController. 13 | 14 | final class RAImagePickerDataSource : NSObject, UICollectionViewDataSource { 15 | 16 | var layoutModel = RALayoutModel.empty 17 | var cellRegistrator: RACellRegistrator? 18 | var assetsModel: RAImagePickerAssetModel 19 | 20 | init(assetsModel: RAImagePickerAssetModel) { 21 | self.assetsModel = assetsModel 22 | super.init() 23 | } 24 | 25 | func numberOfSections(in collectionView: UICollectionView) -> Int { 26 | return layoutModel.numberOfSections 27 | } 28 | 29 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 30 | return layoutModel.numberOfItems(in: section) 31 | } 32 | 33 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 34 | 35 | guard let cellsRegistrator = cellRegistrator else { 36 | fatalError("cells registrator must be set at this moment") 37 | } 38 | 39 | //TODO: change these hardcoded section numbers to those defined in layoutModel.layoutConfiguration 40 | switch indexPath.section { 41 | case 0: 42 | guard let id = cellsRegistrator.cellIdentifier(forActionItemAt: indexPath.row) else { 43 | fatalError("there is an action item at index \(indexPath.row) but no cell is registered.") 44 | } 45 | return collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath) 46 | 47 | case 1: 48 | let id = cellsRegistrator.cameraItemIdentifierPrefix 49 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath) as? RACameraCollectionViewCell else { 50 | fatalError("there is a camera item but no cell class `CameraCollectionViewCell` is registered.") 51 | } 52 | return cell 53 | 54 | case 2: 55 | 56 | let asset = assetsModel.fetchResult.object(at: indexPath.item) 57 | let cellId = cellsRegistrator.cellIdentifier(forAsset: asset.mediaType) ?? cellsRegistrator.cellIdentifierForAssetItems 58 | 59 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as? RAImagePickerAssetCell else { 60 | fatalError("asset item cell must conform to \(RAImagePickerAssetCell.self) protocol") 61 | } 62 | 63 | let thumbnailSize = assetsModel.thumbnailSize ?? .zero 64 | 65 | // Request an image for the asset from the PHCachingImageManager. 66 | cell.representedAssetIdentifier = asset.localIdentifier 67 | assetsModel.imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil, resultHandler: { image, _ in 68 | // The cell may have been recycled by the time this handler gets called; 69 | // set the cell's thumbnail image only if it's still showing the same asset. 70 | if cell.representedAssetIdentifier == asset.localIdentifier && image != nil { 71 | cell.imageView.image = image 72 | } 73 | }) 74 | 75 | return cell as! UICollectionViewCell 76 | 77 | default: fatalError("only 3 sections are supported") 78 | } 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Processing/RAImagePickerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAImagePickerDelegate.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol ImagePickerDelegate : class { 11 | 12 | // Selects one of action items 13 | func imagePicker(delegate: RAImagePickerDelegate, didSelectActionItemAt index: Int) 14 | 15 | // Selects one of asset items 16 | func imagePicker(delegate: RAImagePickerDelegate, didSelectAssetItemAt index: Int) 17 | 18 | // Deselects one of selected asset items 19 | func imagePicker(delegate: RAImagePickerDelegate, didDeselectAssetItemAt index: Int) 20 | 21 | // Action item is about to be displayed 22 | func imagePicker(delegate: RAImagePickerDelegate, willDisplayActionCell cell: UICollectionViewCell, at index: Int) 23 | 24 | // Camera item is about to be displayed 25 | func imagePicker(delegate: RAImagePickerDelegate, willDisplayCameraCell cell: RACameraCollectionViewCell) 26 | 27 | // Camera item ended displaying 28 | func imagePicker(delegate: RAImagePickerDelegate, didEndDisplayingCameraCell cell: RACameraCollectionViewCell) 29 | 30 | func imagePicker(delegate: RAImagePickerDelegate, willDisplayAssetCell cell: RAImagePickerAssetCell, at index: Int) 31 | 32 | func imagePicker(delegate: RAImagePickerDelegate, didScroll scrollView: UIScrollView) 33 | } 34 | 35 | final class RAImagePickerDelegate : NSObject, UICollectionViewDelegateFlowLayout { 36 | 37 | var layout: RAImagePickerLayout? 38 | weak var delegate: ImagePickerDelegate? 39 | 40 | private let selectionPolicy = RAImagePickerSelectionPolicy() 41 | 42 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 43 | return layout?.collectionView(collectionView, layout: collectionViewLayout, sizeForItemAt: indexPath) ?? .zero 44 | } 45 | 46 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 47 | return layout?.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: section) ?? .zero 48 | } 49 | 50 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 51 | if indexPath.section == layout?.configuration.sectionIndexForAssets { 52 | delegate?.imagePicker(delegate: self, didSelectAssetItemAt: indexPath.row) 53 | } 54 | } 55 | 56 | func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { 57 | if indexPath.section == layout?.configuration.sectionIndexForAssets { 58 | delegate?.imagePicker(delegate: self, didDeselectAssetItemAt: indexPath.row) 59 | } 60 | } 61 | 62 | func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { 63 | guard let configuration = layout?.configuration else { return false } 64 | return selectionPolicy.shouldSelectItem(atSection: indexPath.section, layoutConfiguration: configuration) 65 | } 66 | 67 | func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { 68 | guard let configuration = layout?.configuration else { return false } 69 | return selectionPolicy.shouldHighlightItem(atSection: indexPath.section, layoutConfiguration: configuration) 70 | } 71 | 72 | func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { 73 | if indexPath.section == layout?.configuration.sectionIndexForActions { 74 | delegate?.imagePicker(delegate: self, didSelectActionItemAt: indexPath.row) 75 | } 76 | } 77 | 78 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 79 | 80 | guard let configuration = layout?.configuration else { return } 81 | 82 | switch indexPath.section { 83 | case configuration.sectionIndexForActions: delegate?.imagePicker(delegate: self, willDisplayActionCell: cell, at: indexPath.row) 84 | case configuration.sectionIndexForCamera: delegate?.imagePicker(delegate: self, willDisplayCameraCell: cell as! RACameraCollectionViewCell) 85 | case configuration.sectionIndexForAssets: delegate?.imagePicker(delegate: self, willDisplayAssetCell: cell as! RAImagePickerAssetCell, at: indexPath.row) 86 | default: fatalError("index path not supported") 87 | } 88 | } 89 | 90 | func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 91 | 92 | guard let configuration = layout?.configuration else { return } 93 | 94 | switch indexPath.section { 95 | case configuration.sectionIndexForCamera: delegate?.imagePicker(delegate: self, didEndDisplayingCameraCell: cell as! RACameraCollectionViewCell) 96 | case configuration.sectionIndexForActions, configuration.sectionIndexForAssets: break 97 | default: fatalError("index path not supported") 98 | } 99 | } 100 | 101 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 102 | delegate?.imagePicker(delegate: self, didScroll: scrollView) 103 | } 104 | 105 | @available(iOS 11.0, *) 106 | func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { 107 | print("XXX: \(scrollView.adjustedContentInset)") 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Processing/RAImagePickerLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAImagePickerLayout.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import UIKit 9 | 10 | /* 11 | Helper class that contains logic doing layout of collection view cells. UICollectionViewFlowLayout Workaround 12 | */ 13 | 14 | final class RAImagePickerLayout { 15 | 16 | var configuration: RALayoutConfiguration 17 | 18 | init(configuration: RALayoutConfiguration) { 19 | self.configuration = configuration 20 | } 21 | 22 | /* 23 | Returns the size for item considering the number of rows and scroll direction. 24 | if preferredWidthOrHeight == nil, then square size is returned 25 | */ 26 | func sizeForItem(numberOfItemsInRow: Int, preferredWidthOrHeight: CGFloat?, collectionView: UICollectionView, scrollDirection: UICollectionViewScrollDirection) -> CGSize { 27 | 28 | switch scrollDirection { 29 | case .horizontal: 30 | var itemHeight = collectionView.frame.height 31 | itemHeight -= (collectionView.contentInset.top + collectionView.contentInset.bottom) 32 | itemHeight -= (CGFloat(numberOfItemsInRow) - 1) * configuration.interitemSpacing 33 | itemHeight /= CGFloat(numberOfItemsInRow) 34 | return CGSize(width: preferredWidthOrHeight ?? itemHeight, height: itemHeight) 35 | 36 | case .vertical: 37 | var itemWidth = collectionView.frame.width 38 | itemWidth -= (collectionView.contentInset.left + collectionView.contentInset.right) 39 | itemWidth -= (CGFloat(numberOfItemsInRow) - 1) * configuration.interitemSpacing 40 | itemWidth /= CGFloat(numberOfItemsInRow) 41 | return CGSize(width: itemWidth, height: preferredWidthOrHeight ?? itemWidth) 42 | } 43 | } 44 | 45 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 46 | 47 | guard let layout = collectionViewLayout as? UICollectionViewFlowLayout else { 48 | fatalError("currently only UICollectionViewFlowLayout is supported") 49 | } 50 | 51 | let layoutModel = RALayoutModel(configuration: configuration, assets: 0) 52 | 53 | switch indexPath.section { 54 | case configuration.sectionIndexForActions: 55 | /* 56 | This will make sure that action item is either square if there are 2 items or rectangle if there is only 1 item 57 | */ 58 | //let width = sizeForItem(numberOfItemsInRow: 2, preferredWidthOrHeight: nil, collectionView: collectionView, scrollDirection: layout.scrollDirection).width 59 | let ratio: CGFloat = 0.25 60 | let width = collectionView.frame.width * ratio 61 | return sizeForItem(numberOfItemsInRow: layoutModel.numberOfItems(in: configuration.sectionIndexForActions), preferredWidthOrHeight: width, collectionView: collectionView, scrollDirection: layout.scrollDirection) 62 | 63 | case configuration.sectionIndexForCamera: 64 | //lets keep this ratio so camera item is a nice rectangle 65 | 66 | let traitCollection = collectionView.traitCollection 67 | 68 | var ratio: CGFloat = 160/212 69 | 70 | // For landscape we need different ratio 71 | switch traitCollection.userInterfaceIdiom { 72 | case .phone: 73 | switch (traitCollection.horizontalSizeClass, traitCollection.verticalSizeClass) { 74 | // Sizes for iPhone Landscape 75 | case (.unspecified, .compact): 76 | fallthrough 77 | // Sizes for iPhone+ Landscape 78 | case (.regular, .compact): 79 | fallthrough 80 | // Sizes for iPhones in Landscape except iPhone+ 81 | case (.compact, .compact): 82 | ratio = 1/ratio 83 | default: break 84 | } 85 | 86 | default: 87 | break 88 | } 89 | 90 | let widthOrHeight: CGFloat = collectionView.frame.height * ratio 91 | return sizeForItem(numberOfItemsInRow: layoutModel.numberOfItems(in: configuration.sectionIndexForCamera), preferredWidthOrHeight: widthOrHeight, collectionView: collectionView, scrollDirection: layout.scrollDirection) 92 | 93 | case configuration.sectionIndexForAssets: 94 | // Make sure there is at least 1 item, othewise invalid layout 95 | assert(configuration.numberOfAssetItemsInRow > 0, "invalid layout - numberOfAssetItemsInRow must be > 0, check your layout configuration ") 96 | return sizeForItem(numberOfItemsInRow: configuration.numberOfAssetItemsInRow, preferredWidthOrHeight: nil, collectionView: collectionView, scrollDirection: layout.scrollDirection) 97 | 98 | default: 99 | fatalError("unexpected sections count") 100 | } 101 | 102 | } 103 | 104 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 105 | 106 | guard let layout = collectionViewLayout as? UICollectionViewFlowLayout else { 107 | fatalError("currently only UICollectionViewFlowLayout is supported") 108 | } 109 | 110 | /// helper method that creates edge insets considering scroll direction 111 | func sectionInsets(_ inset: CGFloat) -> UIEdgeInsets { 112 | switch layout.scrollDirection { 113 | case .horizontal: return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: inset) 114 | case .vertical: return UIEdgeInsets(top: 0, left: 0, bottom: inset, right: 0) 115 | } 116 | } 117 | 118 | let layoutModel = RALayoutModel(configuration: configuration, assets: 0) 119 | 120 | switch section { 121 | case 0 where layoutModel.numberOfItems(in: section) > 0: 122 | return sectionInsets(configuration.actionSectionSpacing) 123 | case 1 where layoutModel.numberOfItems(in: section) > 0: 124 | return sectionInsets(configuration.cameraSectionSpacing) 125 | default: 126 | return .zero 127 | } 128 | } 129 | 130 | } 131 | 132 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Processing/RAImagePickerSelectionPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePickerSelectionPolicy.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import Foundation 9 | 10 | /* 11 | Determines which cells are selected, multiple selected or highlighted. Allow selecting only asset items, action items are only highlighted, camera item is untouched. 12 | */ 13 | struct RAImagePickerSelectionPolicy { 14 | 15 | func shouldSelectItem(atSection section: Int, layoutConfiguration: RALayoutConfiguration) -> Bool { 16 | switch section { 17 | case layoutConfiguration.sectionIndexForActions, layoutConfiguration.sectionIndexForCamera: 18 | return false 19 | default: 20 | return true 21 | } 22 | } 23 | 24 | func shouldHighlightItem(atSection section: Int, layoutConfiguration: RALayoutConfiguration) -> Bool { 25 | switch section { 26 | case layoutConfiguration.sectionIndexForCamera: 27 | return false 28 | default: 29 | return true 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Processing/RALayoutModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RALayoutModel.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import UIKit 9 | 10 | /* 11 | A model that contains info that is used by layout code and collection view data source when figuring out layout structure. 12 | Contains three sections: 13 | 1. for actions (supports up to 2 action items). 14 | 2. for camera (1 camera item). 15 | 3. for image assets (any number of image asset items). 16 | 17 | Note: Each section can be empty. 18 | */ 19 | 20 | struct RALayoutModel { 21 | 22 | private var sections: [Int] = [0, 0, 0] 23 | 24 | init(configuration: RALayoutConfiguration, assets: Int) { 25 | var actionItems: Int = configuration.showsDefaultCameraItem ? 1 : 0 26 | actionItems += configuration.showsDefaultGalleryItem ? 1 : 0 27 | sections[configuration.sectionIndexForActions] = actionItems 28 | sections[configuration.sectionIndexForCamera] = configuration.showsCameraItem ? 1 : 0 29 | sections[configuration.sectionIndexForAssets] = assets 30 | } 31 | 32 | var numberOfSections: Int { 33 | return sections.count 34 | } 35 | 36 | func numberOfItems(in section: Int) -> Int { 37 | return sections[section] 38 | } 39 | 40 | static var empty: RALayoutModel { 41 | let emptyConfiguration = RALayoutConfiguration(showsDefaultCameraItem: false, showsDefaultGalleryItem: false, showsCameraItem: false, scrollDirection: .horizontal, numberOfAssetItemsInRow: 0, interitemSpacing: 0, actionSectionSpacing: 0, cameraSectionSpacing: 0) 42 | return RALayoutModel(configuration: emptyConfiguration, assets: 0) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Processing/UIImageEffects.h: -------------------------------------------------------------------------------- 1 | /* 2 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 3 | Inc. ("Apple") in consideration of your agreement to the following 4 | terms, and your use, installation, modification or redistribution of 5 | this Apple software constitutes acceptance of these terms. If you do 6 | not agree with these terms, please do not use, install, modify or 7 | redistribute this Apple software. 8 | 9 | In consideration of your agreement to abide by the following terms, and 10 | subject to these terms, Apple grants you a personal, non-exclusive 11 | license, under Apple's copyrights in this original Apple software (the 12 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 13 | Software, with or without modifications, in source and/or binary forms; 14 | provided that if you redistribute the Apple Software in its entirety and 15 | without modifications, you must retain this notice and the following 16 | text and disclaimers in all such redistributions of the Apple Software. 17 | Neither the name, trademarks, service marks or logos of Apple Inc. may 18 | be used to endorse or promote products derived from the Apple Software 19 | without specific prior written permission from Apple. Except as 20 | expressly stated in this notice, no other rights or licenses, express or 21 | implied, are granted by Apple herein, including but not limited to any 22 | patent rights that may be infringed by your derivative works or by other 23 | works in which the Apple Software may be incorporated. 24 | 25 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 26 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 27 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 28 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 29 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 30 | 31 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 32 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 35 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 36 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 37 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 38 | POSSIBILITY OF SUCH DAMAGE. 39 | 40 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 41 | 42 | */ 43 | 44 | #import 45 | #import 46 | 47 | @interface UIImageEffects : NSObject 48 | 49 | + (UIImage*)imageByApplyingLightEffectToImage:(UIImage*)inputImage; 50 | + (UIImage*)imageByApplyingExtraLightEffectToImage:(UIImage*)inputImage; 51 | + (UIImage*)imageByApplyingDarkEffectToImage:(UIImage*)inputImage; 52 | + (UIImage*)imageByApplyingTintEffectWithColor:(UIColor *)tintColor toImage:(UIImage*)inputImage; 53 | + (UIImage*)imageByApplyingBlurToImage:(UIImage*)inputImage 54 | withRadius:(CGFloat)blurRadius 55 | tintColor:(UIColor *)tintColor 56 | saturationDeltaFactor:(CGFloat)saturationDeltaFactor 57 | maskImage:(UIImage *)maskImage; 58 | 59 | @end 60 | 61 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Public/Layout/RAAppearance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAAppearance.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 11/29/17. 6 | // 7 | 8 | import UIKit 9 | import AVFoundation 10 | 11 | // Provides access to styling attributes of ImagePicker 12 | 13 | public class RAAppearance { 14 | 15 | // ImagePicker background color 16 | public var backgroundColor: UIColor = UIColor(red: 208/255, green: 213/255, blue: 218/255, alpha: 1) 17 | } 18 | 19 | extension UIInterfaceOrientation { 20 | 21 | var captureVideoOrientation: AVCaptureVideoOrientation { 22 | switch self { 23 | case .portrait, .unknown: return .portrait 24 | case .portraitUpsideDown: return .portraitUpsideDown 25 | case .landscapeRight: return .landscapeRight 26 | case .landscapeLeft: return .landscapeLeft 27 | } 28 | } 29 | } 30 | extension UIInterfaceOrientation : CustomDebugStringConvertible { 31 | 32 | public var debugDescription: String { 33 | switch self { 34 | case .unknown: return "unknown" 35 | case .portrait: return "portrait" 36 | case .portraitUpsideDown: return "portrait upside down" 37 | case .landscapeRight: return "landscape right" 38 | case .landscapeLeft: return "landscape left" 39 | } 40 | } 41 | } 42 | 43 | extension UICollectionView { 44 | 45 | func indexPathsForElements(in rect: CGRect) -> [IndexPath] { 46 | let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect)! 47 | let paths = allLayoutAttributes.map { $0.indexPath } 48 | return paths 49 | } 50 | } 51 | 52 | // MARK: - Functions 53 | func differencesBetweenRects(_ old: CGRect, _ new: CGRect, _ scrollDirection: UICollectionViewScrollDirection) -> (added: [CGRect], removed: [CGRect]) { 54 | switch scrollDirection { 55 | case .horizontal: return differencesBetweenRectsHorizontal(old, new) 56 | case .vertical: return differencesBetweenRectsVertical(old, new) 57 | } 58 | } 59 | 60 | func differencesBetweenRectsVertical(_ old: CGRect, _ new: CGRect) -> (added: [CGRect], removed: [CGRect]) { 61 | if old.intersects(new) { 62 | var added = [CGRect]() 63 | if new.maxY > old.maxY { 64 | added += [CGRect(x: new.origin.x, y: old.maxY, width: new.width, height: new.maxY - old.maxY)] 65 | } 66 | if old.minY > new.minY { 67 | added += [CGRect(x: new.origin.x, y: new.minY, width: new.width, height: old.minY - new.minY)] 68 | } 69 | var removed = [CGRect]() 70 | if new.maxY < old.maxY { 71 | removed += [CGRect(x: new.origin.x, y: new.maxY, width: new.width, height: old.maxY - new.maxY)] 72 | } 73 | if old.minY < new.minY { 74 | removed += [CGRect(x: new.origin.x, y: old.minY, width: new.width, height: new.minY - old.minY)] 75 | } 76 | return (added, removed) 77 | } 78 | else { 79 | return ([new], [old]) 80 | } 81 | } 82 | 83 | func differencesBetweenRectsHorizontal(_ old: CGRect, _ new: CGRect) -> (added: [CGRect], removed: [CGRect]) { 84 | if old.intersects(new) { 85 | var added = [CGRect]() 86 | if new.maxX > old.maxX { 87 | added += [CGRect(x: old.maxX, y: old.origin.y, width: new.maxX - old.maxX, height: old.height)] 88 | } 89 | if old.minX > new.minX { 90 | added += [CGRect(x: new.minX, y: old.origin.y, width: old.maxX - new.maxX, height: old.height)] 91 | } 92 | var removed = [CGRect]() 93 | if new.maxX < old.maxX { 94 | removed += [CGRect(x: new.maxX, y: old.origin.y, width: old.maxX - new.maxX, height: old.height)] 95 | } 96 | if old.minX < new.minX { 97 | removed += [CGRect(x: old.minX, y: old.origin.y, width: new.maxX - old.maxX, height: old.height)] 98 | } 99 | return (added, removed) 100 | } 101 | else { 102 | return ([new], [old]) 103 | } 104 | } 105 | 106 | extension UIImage { 107 | static func fromWrappedBundleImage(_ wrappedImage: WrappedBundleImage) -> UIImage { 108 | return wrappedImage.image 109 | } 110 | } 111 | struct WrappedBundleImage: _ExpressibleByImageLiteral { 112 | let image: UIImage 113 | 114 | init(imageLiteralResourceName name: String) { 115 | let bundle = Bundle(for: RAActionCell.self) 116 | image = UIImage(named: name, in: bundle, compatibleWith: nil)! 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Public/Layout/RACameraCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RACameraCollectionViewCell.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 11/29/17. 6 | // 7 | 8 | import UIKit 9 | import Foundation 10 | import AVFoundation 11 | 12 | protocol RACameraCollectionViewCellDelegate : class { 13 | func takePicture() 14 | func takeLivePhoto() 15 | func startVideoRecording() 16 | func stopVideoRecording() 17 | func flipCamera(_ completion: (() -> Void)?) 18 | } 19 | 20 | /* 21 | Base class for cutom camera cells. [Custom Cell --inherit--> RACameraCollectionViewCell] 22 | */ 23 | open class RACameraCollectionViewCell: UICollectionViewCell { 24 | 25 | // Video Preview Layer 26 | var previewView: AVPreviewView = { 27 | let view = AVPreviewView(frame: .zero) 28 | view.backgroundColor = UIColor.black 29 | return view 30 | }() 31 | // Adding an image above the blur view so the image can hide the black background 32 | var imageView: UIImageView = { 33 | let view = UIImageView(frame: .zero) 34 | view.contentMode = .scaleAspectFill 35 | return view 36 | }() 37 | 38 | var blurView: UIVisualEffectView? 39 | var isVisualEffectViewUsedForBlurring = false 40 | weak var delegate: RACameraCollectionViewCellDelegate? 41 | 42 | // MARK: - Initialization 43 | required public init?(coder aDecoder: NSCoder) { 44 | super.init(coder: aDecoder) 45 | backgroundView = previewView 46 | previewView.addSubview(imageView) 47 | } 48 | public override init(frame: CGRect) { 49 | super.init(frame: frame) 50 | backgroundView = previewView 51 | previewView.addSubview(imageView) 52 | } 53 | open override func layoutSubviews() { 54 | super.layoutSubviews() 55 | imageView.frame = previewView.bounds 56 | blurView?.frame = previewView.bounds 57 | } 58 | 59 | // MARK: - Public Methods 60 | 61 | // Camera authorization status delegate. [Update UI based on the value of `authorizationStatus` property] 62 | open func updateCameraAuthorizationStatus() {} 63 | 64 | // Cell can have multiple visual states based on autorization status. 65 | public internal(set) var authorizationStatus: AVAuthorizationStatus? { 66 | didSet { 67 | updateCameraAuthorizationStatus() 68 | } 69 | } 70 | 71 | // Live Photos delegate. 72 | // - isProcessing: check if there is at least one live photo being processed/captured. 73 | // - shouldAnimate: check if the UI change should be animated or not. 74 | open func updateLivePhotoStatus(isProcessing: Bool, shouldAnimate: Bool) {} 75 | 76 | // Video recording delegate. [Override this method to update UI based on recording status] 77 | // - isRecording: check if the video is recording or not. 78 | // - shouldAnimate: check if the UI change should be animated or not. 79 | open func updateRecordingVideoStatus(isRecording: Bool, shouldAnimate: Bool) {} 80 | // Video became ready 81 | open func videoRecodingDidBecomeReady() {} 82 | 83 | /* 84 | Camera Functionalities 85 | */ 86 | // Camera Flipping 87 | @objc public func flipCamera(_ completion: (() -> Void)? = nil) { 88 | delegate?.flipCamera(completion) 89 | } 90 | // Capture Photos 91 | @objc public func takePicture() { 92 | delegate?.takePicture() 93 | } 94 | // Capture Live Photos 95 | @objc public func takeLivePhoto() { 96 | delegate?.takeLivePhoto() 97 | } 98 | // Record Video 99 | @objc public func startVideoRecording() { 100 | delegate?.startVideoRecording() 101 | } 102 | @objc public func stopVideoRecording() { 103 | delegate?.stopVideoRecording() 104 | } 105 | 106 | /* 107 | Blur Effect 108 | */ 109 | func blurEffect(blurImage: UIImage?, animated: Bool, completion: ((Bool) -> Void)?) { 110 | var view: UIView 111 | if isVisualEffectViewUsedForBlurring == false { 112 | guard imageView.image == nil else { 113 | return 114 | } 115 | imageView.image = blurImage 116 | view = imageView 117 | } 118 | else { 119 | if blurView == nil { 120 | blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) 121 | previewView.addSubview(blurView!) 122 | } 123 | view = blurView! 124 | view.frame = previewView.bounds 125 | } 126 | view.alpha = 0 127 | if animated == false { 128 | view.alpha = 1 129 | completion?(true) 130 | } 131 | else { 132 | UIView.animate(withDuration: 0.1, delay: 0, options: .allowAnimatedContent, animations: { 133 | view.alpha = 1 134 | }, completion: completion) 135 | } 136 | } 137 | func unblurEffect(unblurImage: UIImage?, animated: Bool, completion: ((Bool) -> Void)?) { 138 | var animationBlock: () -> () 139 | var animationCompletionBlock: (Bool) -> () 140 | if isVisualEffectViewUsedForBlurring == false { 141 | guard imageView.image != nil else { 142 | return 143 | } 144 | if let image = unblurImage { 145 | imageView.image = image 146 | } 147 | animationBlock = { 148 | self.imageView.alpha = 0 149 | } 150 | animationCompletionBlock = { finished in 151 | self.imageView.image = nil 152 | completion?(finished) 153 | } 154 | } 155 | else { 156 | animationBlock = { 157 | self.blurView?.alpha = 0 158 | } 159 | animationCompletionBlock = { finished in 160 | completion?(finished) 161 | } 162 | } 163 | if animated == false { 164 | animationBlock() 165 | animationCompletionBlock(true) 166 | } 167 | else { 168 | UIView.animate(withDuration: 0.1, delay: 0, options: .allowAnimatedContent, animations: animationBlock, completion: animationCompletionBlock) 169 | } 170 | } 171 | 172 | // Tap press determining wether is should take a photo or not. 173 | func touchIsCaptureEffective(point: CGPoint) -> Bool { 174 | // Finding topmost view that detected the touch at a point and check if its not any anything other than contentView. 175 | if bounds.contains(point), let testedView = hitTest(point, with: nil), testedView === contentView { 176 | return true 177 | } 178 | return false 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Public/Layout/RALayoutConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RALayoutConfiguration.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 11/29/17. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | // Helper struct thats used with the ImagePickerLayout when configuring and laying out CollectionView items. 12 | public struct RALayoutConfiguration { 13 | 14 | public var showsDefaultCameraItem = true 15 | public var showsDefaultGalleryItem = true 16 | public var showsCameraItem = true 17 | 18 | let showsAssetItems = true 19 | 20 | // Scroll and Layout Direction 21 | public var scrollDirection: UICollectionViewScrollDirection = .horizontal 22 | 23 | // Define the number of image assets will be in a row 24 | public var numberOfAssetItemsInRow: Int = 2 25 | 26 | // Space between items within a section 27 | public var interitemSpacing: CGFloat = 1 28 | 29 | // Spacing between actions section and camera section 30 | public var actionSectionSpacing: CGFloat = 1 31 | 32 | // Spacing between camera section and assets section 33 | public var cameraSectionSpacing: CGFloat = 10 34 | } 35 | 36 | extension RALayoutConfiguration { 37 | 38 | var hasAnyAction: Bool { 39 | return showsDefaultCameraItem || showsDefaultGalleryItem 40 | } 41 | var sectionIndexForActions: Int { 42 | return 0 43 | } 44 | var sectionIndexForCamera: Int { 45 | return 1 46 | } 47 | var sectionIndexForAssets: Int { 48 | return 2 49 | } 50 | 51 | public static var `default` = RALayoutConfiguration() 52 | } 53 | 54 | extension UICollectionView { 55 | 56 | // Method for convenienet access to camera cell 57 | func cameraCell(layout: RALayoutConfiguration) -> RACameraCollectionViewCell? { 58 | return cellForItem(at: IndexPath(row: 0, section: layout.sectionIndexForCamera)) as? RACameraCollectionViewCell 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Public/RACaptureSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RACaptureSettings.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 11/29/17. 6 | // 7 | 8 | import UIKit 9 | 10 | /* 11 | Capture Structure Configuration 12 | */ 13 | public struct RACaptureSettings { 14 | 15 | public enum CameraMode { 16 | 17 | // Support Photos Only [Default] 18 | case photo 19 | 20 | // Support Videos Only 21 | case video 22 | 23 | // Support Photos & Live Photos 24 | case photoAndLivePhoto 25 | 26 | // Support Videos & Photos 27 | case photoAndVideo 28 | } 29 | 30 | 31 | // Preset of media types needed to support [Can not be changed at the runtime] 32 | public var cameraMode: CameraMode 33 | 34 | // Check if saving photos flag is turned on [(Photos Only), Videos and Live Photos is always true] 35 | // save photo in library => true, do not save => false 36 | public var savesCapturedPhotosToPhotoLibrary: Bool 37 | 38 | let savesCapturedLivePhotosToPhotoLibrary: Bool = true 39 | let savesCapturedVideosToPhotoLibrary: Bool = true 40 | 41 | // Default Configuration 42 | public static var `default`: RACaptureSettings { 43 | return RACaptureSettings( 44 | cameraMode: .photo, 45 | savesCapturedPhotosToPhotoLibrary: false 46 | ) 47 | } 48 | } 49 | 50 | extension RACaptureSettings.CameraMode { 51 | /// transforms user related enum to specific internal capture session enum 52 | 53 | var captureSessionPresetConfiguration: RACaptureSession.SessionPresetConfiguration { 54 | switch self { 55 | case .photo: return .photos 56 | case .video: return .videos 57 | case .photoAndLivePhoto: return .livePhotos 58 | case .photoAndVideo: return .videos 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/Cells/RAActionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAActionCell.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import UIKit 9 | import Foundation 10 | 11 | 12 | final class RAActionCell: UICollectionViewCell { 13 | 14 | @IBOutlet weak var titleLabel: UILabel! 15 | @IBOutlet weak var imageView: UIImageView! 16 | 17 | @IBOutlet var leadingOffset: NSLayoutConstraint! 18 | @IBOutlet var trailingOffset: NSLayoutConstraint! 19 | @IBOutlet var topOffset: NSLayoutConstraint! 20 | @IBOutlet var bottomOffset: NSLayoutConstraint! 21 | 22 | override func awakeFromNib() { 23 | super.awakeFromNib() 24 | // Initialization code 25 | 26 | imageView.backgroundColor = UIColor.clear 27 | } 28 | } 29 | 30 | extension RAActionCell { 31 | 32 | func updateCell(cameraText:String, cameraIconColor: UIColor, 33 | galleryText: String, galleryIconColor: UIColor, index: Int) 34 | { 35 | titleLabel.textColor = .black 36 | switch index { 37 | case 0: 38 | titleLabel.text = cameraText 39 | imageView.image = imageView.image!.withRenderingMode(.alwaysTemplate) 40 | imageView.tintColor = cameraIconColor 41 | break 42 | case 1: 43 | titleLabel.text = galleryText 44 | imageView.image = imageView.image!.withRenderingMode(.alwaysTemplate) 45 | imageView.tintColor = galleryIconColor 46 | break 47 | default: 48 | break 49 | } 50 | } 51 | 52 | func update(withIndex index: Int, layoutConfiguration: RALayoutConfiguration) { 53 | 54 | // Layout 55 | let layoutModel = RALayoutModel(configuration: layoutConfiguration, assets: 0) 56 | let actionCount = layoutModel.numberOfItems(in: layoutConfiguration.sectionIndexForActions) 57 | 58 | titleLabel.textColor = .black 59 | switch index { 60 | case 0: 61 | titleLabel.text = "Camera" 62 | imageView.image = UIImage.fromWrappedBundleImage(#imageLiteral(resourceName: "icon-camera")) 63 | imageView.image = imageView.image!.withRenderingMode(.alwaysTemplate) 64 | imageView.tintColor = .black 65 | break 66 | case 1: 67 | titleLabel.text = "Gallery" 68 | imageView.image = UIImage.fromWrappedBundleImage(#imageLiteral(resourceName: "icon-gallery")) 69 | imageView.image = imageView.image!.withRenderingMode(.alwaysTemplate) 70 | imageView.tintColor = .black 71 | break 72 | default: 73 | break 74 | } 75 | 76 | let isFirst = index == 0 77 | let isLast = index == actionCount - 1 78 | 79 | switch layoutConfiguration.scrollDirection { 80 | case .horizontal: 81 | topOffset.constant = isFirst ? 10 : 5 82 | bottomOffset.constant = isLast ? 10 : 5 83 | leadingOffset.constant = 5 84 | trailingOffset.constant = 5 85 | case .vertical: 86 | topOffset.constant = 5 87 | bottomOffset.constant = 5 88 | leadingOffset.constant = isFirst ? 10 : 5 89 | trailingOffset.constant = isLast ? 10 : 5 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/Cells/RAActionCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/Cells/RAAssetCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAAssetCell.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | 9 | import Foundation 10 | import Photos 11 | 12 | // Each image picker asset cell must conform to this protocol. 13 | public protocol RAImagePickerAssetCell : class { 14 | 15 | // This image view will be used when setting an asset's image. 16 | var imageView: UIImageView! { get } 17 | 18 | // This is a helper identifier that is used when properly displaying cells asynchronously. 19 | var representedAssetIdentifier: String? { get set } 20 | } 21 | 22 | /* 23 | Default collection view cell that represents asset item. It supports: 24 | - Shows Image View of Image Thumbnail. 25 | - Icon and Duration for Videos. 26 | - Selected Icon when "isSelected" is true. 27 | */ 28 | class VideoAssetCell : RAAssetCell { 29 | 30 | var durationLabel: UILabel 31 | var iconView: UIImageView 32 | var gradientView: UIImageView 33 | 34 | override init(frame: CGRect) { 35 | 36 | durationLabel = UILabel(frame: .zero) 37 | gradientView = UIImageView(frame: .zero) 38 | iconView = UIImageView(frame: .zero) 39 | 40 | super.init(frame: frame) 41 | 42 | gradientView.isHidden = true 43 | 44 | iconView.tintColor = UIColor.white 45 | iconView.contentMode = .center 46 | 47 | durationLabel.textColor = UIColor.white 48 | durationLabel.font = UIFont.systemFont(ofSize: 12, weight: .semibold) 49 | durationLabel.textAlignment = .right 50 | 51 | contentView.addSubview(gradientView) 52 | contentView.addSubview(durationLabel) 53 | contentView.addSubview(iconView) 54 | } 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | 60 | override func layoutSubviews() { 61 | super.layoutSubviews() 62 | 63 | gradientView.frame.size = CGSize(width: bounds.width, height: 40) 64 | gradientView.frame.origin = CGPoint(x: 0, y: bounds.height-40) 65 | 66 | let margin: CGFloat = 5 67 | durationLabel.frame.size = CGSize(width: 50, height: 20) 68 | durationLabel.frame.origin = CGPoint( 69 | x: contentView.bounds.width - durationLabel.frame.size.width - margin, 70 | y: contentView.bounds.height - durationLabel.frame.size.height - margin 71 | ) 72 | iconView.frame.size = CGSize(width: 21, height: 21) 73 | iconView.frame.origin = CGPoint( 74 | x: margin, 75 | y: contentView.bounds.height - iconView.frame.height - margin 76 | ) 77 | } 78 | 79 | static let durationFormatter: DateComponentsFormatter = { 80 | let formatter = DateComponentsFormatter() 81 | formatter.unitsStyle = .positional 82 | formatter.allowedUnits = [.minute, .second] 83 | formatter.zeroFormattingBehavior = .pad 84 | return formatter 85 | }() 86 | 87 | func update(with asset: PHAsset) { 88 | 89 | switch asset.mediaType { 90 | case .image: 91 | if asset.mediaSubtypes.contains(.photoLive) { 92 | gradientView.isHidden = false 93 | gradientView.image = UIImage(named: "gradient", in: Bundle(for: type(of: self)), compatibleWith: nil)?.resizableImage(withCapInsets: .zero, resizingMode: .stretch) 94 | iconView.isHidden = false 95 | durationLabel.isHidden = true 96 | iconView.image = UIImage(named: "icon-badge-livephoto", in: Bundle(for: type(of: self)), compatibleWith: nil) 97 | } 98 | else { 99 | gradientView.isHidden = true 100 | iconView.isHidden = true 101 | durationLabel.isHidden = true 102 | } 103 | case .video: 104 | gradientView.isHidden = false 105 | gradientView.image = UIImage(named: "gradient", in: Bundle(for: type(of: self)), compatibleWith: nil)?.resizableImage(withCapInsets: .zero, resizingMode: .stretch) 106 | iconView.isHidden = false 107 | durationLabel.isHidden = false 108 | iconView.image = UIImage(named: "icon-badge-video", in: Bundle(for: type(of: self)), compatibleWith: nil) 109 | durationLabel.text = VideoAssetCell.durationFormatter.string(from: asset.duration) 110 | default: break 111 | } 112 | } 113 | } 114 | 115 | /* 116 | A default implementation of `ImagePickerAssetCell`. If user does not register a custom cell, Image Picker will use this one. Also contains default icon for selected state. 117 | */ 118 | class RAAssetCell : UICollectionViewCell, RAImagePickerAssetCell { 119 | 120 | var imageView: UIImageView! = UIImageView(frame: .zero) 121 | fileprivate var selectedImageView = CheckView(frame: .zero) 122 | 123 | var representedAssetIdentifier: String? 124 | 125 | override var isSelected: Bool { 126 | didSet { 127 | selectedImageView.isHidden = !isSelected 128 | if selectedImageView.isHidden == false { 129 | selectedImageView.image = UIImage(named: "icon-check-background", in: Bundle(for: type(of: self)), compatibleWith: nil) 130 | selectedImageView.foregroundImage = UIImage(named: "icon-check", in: Bundle(for: type(of: self)), compatibleWith: nil) 131 | } 132 | } 133 | } 134 | 135 | override init(frame: CGRect) { 136 | super.init(frame: frame) 137 | imageView.contentMode = .scaleAspectFill 138 | imageView.clipsToBounds = true 139 | contentView.addSubview(imageView) 140 | 141 | selectedImageView.frame = CGRect(x: 0, y: 0, width: 31, height: 31) 142 | 143 | contentView.addSubview(selectedImageView) 144 | selectedImageView.isHidden = true 145 | } 146 | 147 | required init?(coder aDecoder: NSCoder) { 148 | fatalError("init(coder:) has not been implemented") 149 | } 150 | 151 | override func prepareForReuse() { 152 | super.prepareForReuse() 153 | imageView.image = nil 154 | } 155 | 156 | override func layoutSubviews() { 157 | super.layoutSubviews() 158 | imageView.frame = bounds 159 | let margin: CGFloat = 5 160 | selectedImageView.frame.origin = CGPoint( 161 | x: bounds.width - selectedImageView.frame.width - margin, 162 | y: margin 163 | ) 164 | } 165 | 166 | } 167 | 168 | private final class CheckView : UIImageView { 169 | 170 | var foregroundImage: UIImage? { 171 | get { return foregroundView.image } 172 | set { foregroundView.image = newValue } 173 | } 174 | 175 | private let foregroundView = UIImageView(frame: .zero) 176 | 177 | override init(frame: CGRect) { 178 | super.init(frame: frame) 179 | addSubview(foregroundView) 180 | contentMode = .center 181 | foregroundView.contentMode = .center 182 | } 183 | 184 | required init?(coder aDecoder: NSCoder) { 185 | fatalError("init(coder:) has not been implemented") 186 | } 187 | 188 | override func layoutSubviews() { 189 | super.layoutSubviews() 190 | foregroundView.frame = bounds 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/Cells/RAImagePickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAImagePickerView.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import UIKit 9 | 10 | final class RAImagePickerView : UIView { 11 | 12 | @IBOutlet weak var collectionView: UICollectionView! 13 | } 14 | 15 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/Cells/RAImagePickerView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/Cells/RALivePhotoCameraCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RALivePhotoCameraCell.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import UIKit 9 | import Foundation 10 | 11 | class RALivePhotoCameraCell : RACameraCollectionViewCell { 12 | 13 | @IBOutlet weak var snapButton: UIButton! 14 | @IBOutlet weak var enableLivePhotosButton: RAStationaryButton! 15 | @IBOutlet weak var liveIndicator: RACarvedLabel! 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | liveIndicator.alpha = 0 20 | liveIndicator.tintColor = UIColor(red: 245/255, green: 203/255, blue: 47/255, alpha: 1) 21 | 22 | enableLivePhotosButton.unselectedTintColor = UIColor.white 23 | enableLivePhotosButton.selectedTintColor = UIColor(red: 245/255, green: 203/255, blue: 47/255, alpha: 1) 24 | } 25 | 26 | @IBAction func snapButtonTapped(_ sender: UIButton) { 27 | if enableLivePhotosButton.isSelected { 28 | takeLivePhoto() 29 | } 30 | else { 31 | takePicture() 32 | } 33 | } 34 | 35 | @IBAction func flipButtonTapped(_ sender: UIButton) { 36 | flipCamera() 37 | } 38 | 39 | func updateWithCameraMode(_ mode: RACaptureSettings.CameraMode) { 40 | switch mode { 41 | case .photo: 42 | liveIndicator.isHidden = true 43 | enableLivePhotosButton.isHidden = true 44 | case .photoAndLivePhoto: 45 | liveIndicator.isHidden = false 46 | enableLivePhotosButton.isHidden = false 47 | default: 48 | fatalError("Image Picker - unsupported camera mode for \(type(of: self))") 49 | } 50 | } 51 | 52 | override func updateLivePhotoStatus(isProcessing: Bool, shouldAnimate: Bool) { 53 | 54 | let updates: () -> Void = { 55 | self.liveIndicator.alpha = isProcessing ? 1 : 0 56 | } 57 | 58 | shouldAnimate ? UIView.animate(withDuration: 0.25, animations: updates) : updates() 59 | } 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/Cells/RAVideoCameraCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAVideoCameraCell.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import UIKit 9 | import Foundation 10 | 11 | 12 | // TODO: add a recording indicator (red dot with timer) 13 | class RAVideoCameraCell: RACameraCollectionViewCell { 14 | 15 | @IBOutlet weak var recordLabel: RARecordDurationLabel! 16 | @IBOutlet weak var recordButton: RARecordButton! 17 | @IBOutlet weak var flipButton: UIButton! 18 | 19 | @IBAction func recordButtonTapped(_ sender: UIButton) { 20 | if sender.isSelected { 21 | stopVideoRecording() 22 | } 23 | else { 24 | startVideoRecording() 25 | } 26 | } 27 | 28 | @IBAction func flipButtonTapped(_ sender: UIButton) { 29 | flipCamera() 30 | } 31 | 32 | override func awakeFromNib() { 33 | super.awakeFromNib() 34 | // Initialization code 35 | 36 | recordButton.isEnabled = false 37 | recordButton.alpha = 0.5 38 | } 39 | 40 | override func updateRecordingVideoStatus(isRecording: Bool, shouldAnimate: Bool) { 41 | 42 | // Update button state 43 | recordButton.isSelected = isRecording 44 | 45 | // Update duration label 46 | isRecording ? recordLabel.start() : recordLabel.stop() 47 | 48 | // Update other buttons 49 | let updates: () -> Void = { 50 | self.flipButton.alpha = isRecording ? 0 : 1 51 | } 52 | 53 | shouldAnimate ? UIView.animate(withDuration: 0.25, animations: updates) : updates() 54 | } 55 | 56 | override func videoRecodingDidBecomeReady() { 57 | recordButton.isEnabled = true 58 | UIView.animate(withDuration: 0.25) { 59 | self.recordButton.alpha = 1.0 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/Cells/RAVideoCameraCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 33 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/RACarvedLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RACarvedLabel.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import UIKit 9 | 10 | fileprivate typealias TextAttributes = [NSAttributedStringKey: Any] 11 | 12 | /* 13 | RACarvedLabel: a label whose transparent text is carved into solid color. 14 | Note: text is always aligned to center. 15 | */ 16 | @IBDesignable 17 | final class RACarvedLabel : UIView { 18 | 19 | @IBInspectable var text: String? { 20 | didSet { 21 | invalidateIntrinsicContentSize() 22 | setNeedsDisplay() 23 | } 24 | } 25 | 26 | var font: UIFont? { 27 | didSet { 28 | invalidateIntrinsicContentSize() 29 | setNeedsDisplay() 30 | } 31 | } 32 | 33 | @IBInspectable var cornerRadius: CGFloat = 0 { 34 | didSet { setNeedsDisplay() } 35 | } 36 | 37 | @IBInspectable var verticalInset: CGFloat = 0 { 38 | didSet { 39 | invalidateIntrinsicContentSize() 40 | setNeedsDisplay() 41 | } 42 | } 43 | 44 | @IBInspectable var horizontalInset: CGFloat = 0 { 45 | didSet { 46 | invalidateIntrinsicContentSize() 47 | setNeedsDisplay() 48 | } 49 | 50 | } 51 | 52 | override init(frame: CGRect) { 53 | super.init(frame: frame) 54 | _ = backgroundColor 55 | isOpaque = false 56 | } 57 | 58 | required init?(coder aDecoder: NSCoder) { 59 | super.init(coder: aDecoder) 60 | _ = backgroundColor 61 | isOpaque = false 62 | } 63 | 64 | override var backgroundColor: UIColor? { 65 | get { return UIColor.clear } 66 | set { super.backgroundColor = UIColor.clear } 67 | } 68 | 69 | fileprivate var textAttributes: TextAttributes { 70 | let activeFont = font ?? UIFont.systemFont(ofSize: 12, weight: .regular) 71 | return [ 72 | NSAttributedStringKey.font: activeFont 73 | ] 74 | } 75 | 76 | fileprivate var attributedString: NSAttributedString { 77 | return NSAttributedString(string: text ?? "", attributes: textAttributes) 78 | } 79 | 80 | override func draw(_ rect: CGRect) { 81 | let color = tintColor! 82 | color.setFill() 83 | 84 | let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius) 85 | path.fill() 86 | 87 | guard let context = UIGraphicsGetCurrentContext(), (text?.count ?? 0) > 0 else { 88 | return 89 | } 90 | 91 | let attributedString = self.attributedString 92 | let stringSize = attributedString.size() 93 | 94 | let xOrigin: CGFloat = max(horizontalInset, (rect.width - stringSize.width)/2) 95 | let yOrigin: CGFloat = max(verticalInset, (rect.height - stringSize.height)/2) 96 | 97 | context.saveGState() 98 | context.setBlendMode(.destinationOut) 99 | attributedString.draw(at: CGPoint(x: xOrigin, y: yOrigin)) 100 | context.restoreGState() 101 | } 102 | 103 | override func sizeThatFits(_ size: CGSize) -> CGSize { 104 | let stringSize = attributedString.size() 105 | return CGSize(width: stringSize.width + horizontalInset*2, height: stringSize.height + verticalInset*2) 106 | } 107 | 108 | override var intrinsicContentSize: CGSize { 109 | return sizeThatFits(.zero) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/RARecordButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RARecordButton.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import Foundation 9 | 10 | /* 11 | RARecordButton: a rounded button with 2 circles where middle circle animates based on 3 states - initial, pressed, recording. 12 | */ 13 | class RARecordButton : RAStationaryButton { 14 | 15 | var outerBorderWidth: CGFloat = 3 { didSet { setNeedsUpdateCircleLayers() } } 16 | var innerBorderWidth: CGFloat = 1.5 { didSet { setNeedsUpdateCircleLayers() } } 17 | var pressDepthFactor: CGFloat = 0.9 { didSet { setNeedsUpdateCircleLayers() } } 18 | 19 | override var isHighlighted: Bool { 20 | get { return super.isHighlighted } 21 | set { 22 | if isSelected == false && newValue != isHighlighted && newValue == true { 23 | updateCircleLayers(state: .pressed, animated: true) 24 | } 25 | super.isHighlighted = newValue 26 | } 27 | } 28 | 29 | override func selectionDidChange(animated: Bool) { 30 | super.selectionDidChange(animated: animated) 31 | 32 | if isSelected { 33 | updateCircleLayers(state: .recording, animated: animated) 34 | } 35 | else { 36 | updateCircleLayers(state: .initial, animated: animated) 37 | } 38 | } 39 | 40 | private var innerCircleLayerInset: CGFloat { 41 | return outerBorderWidth + innerBorderWidth 42 | } 43 | 44 | private var needsUpdateCircleLayers = true 45 | private var outerCircleLayer: CALayer 46 | private var innerCircleLayer: CALayer 47 | 48 | private enum State: String { 49 | case initial 50 | case pressed 51 | case recording 52 | } 53 | 54 | private var layersState: State = .initial 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | outerCircleLayer = CALayer() 58 | innerCircleLayer = CALayer() 59 | super.init(coder: aDecoder) 60 | backgroundColor = UIColor.clear 61 | layer.addSublayer(outerCircleLayer) 62 | layer.addSublayer(innerCircleLayer) 63 | CATransaction.setDisableActions(true) 64 | 65 | outerCircleLayer.backgroundColor = UIColor.clear.cgColor 66 | outerCircleLayer.cornerRadius = bounds.width/2 67 | outerCircleLayer.borderWidth = outerBorderWidth 68 | outerCircleLayer.borderColor = tintColor.cgColor 69 | 70 | innerCircleLayer.backgroundColor = UIColor.red.cgColor 71 | 72 | CATransaction.commit() 73 | } 74 | 75 | override func layoutSubviews() { 76 | super.layoutSubviews() 77 | if needsUpdateCircleLayers { 78 | CATransaction.setDisableActions(true) 79 | outerCircleLayer.frame = bounds 80 | innerCircleLayer.frame = bounds.insetBy(dx: innerCircleLayerInset, dy: innerCircleLayerInset) 81 | innerCircleLayer.cornerRadius = bounds.insetBy(dx: innerCircleLayerInset, dy: innerCircleLayerInset).width/2 82 | needsUpdateCircleLayers = false 83 | CATransaction.commit() 84 | } 85 | } 86 | 87 | private func setNeedsUpdateCircleLayers() { 88 | needsUpdateCircleLayers = true 89 | setNeedsLayout() 90 | } 91 | 92 | private func updateCircleLayers(state: State, animated: Bool) { 93 | guard layersState != state else { return } 94 | 95 | layersState = state 96 | 97 | switch layersState { 98 | case .initial: 99 | setInnerLayer(recording: false, animated: animated) 100 | case .pressed: 101 | setInnerLayerPressed(animated: animated) 102 | case .recording: 103 | setInnerLayer(recording: true, animated: animated) 104 | } 105 | } 106 | 107 | private func setInnerLayerPressed(animated: Bool) { 108 | 109 | if animated { 110 | innerCircleLayer.add(transformAnimation(to: pressDepthFactor, duration: 0.25), forKey: nil) 111 | } 112 | else { 113 | CATransaction.setDisableActions(true) 114 | innerCircleLayer.setValue(pressDepthFactor, forKeyPath: "transform.scale") 115 | CATransaction.commit() 116 | } 117 | } 118 | 119 | private func setInnerLayer(recording: Bool, animated: Bool) { 120 | 121 | if recording { 122 | innerCircleLayer.add(transformAnimation(to: 0.5, duration: 0.15), forKey: nil) 123 | innerCircleLayer.cornerRadius = 8 124 | } 125 | else { 126 | innerCircleLayer.add(transformAnimation(to: 1, duration: 0.25), forKey: nil) 127 | innerCircleLayer.cornerRadius = bounds.insetBy(dx: innerCircleLayerInset, dy: innerCircleLayerInset).width/2 128 | } 129 | 130 | } 131 | 132 | private func transformAnimation(to value: CGFloat, duration: CFTimeInterval) -> CAAnimation { 133 | let animation = CABasicAnimation() 134 | animation.keyPath = "transform.scale" 135 | animation.fromValue = innerCircleLayer.presentation()?.value(forKeyPath: "transform.scale") 136 | animation.toValue = value 137 | animation.duration = duration 138 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 139 | animation.beginTime = CACurrentMediaTime() 140 | animation.fillMode = kCAFillModeForwards 141 | animation.isRemovedOnCompletion = false 142 | return animation 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/RARecordDurationLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RARecordDurationLabel.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import UIKit 9 | 10 | /* 11 | RARecordDurationLabel: Label that can be used to show duration during recording or just any duration in general. 12 | */ 13 | final class RARecordDurationLabel : UILabel { 14 | 15 | private var indicatorLayer: CALayer = { 16 | let layer = CALayer() 17 | layer.masksToBounds = true 18 | layer.backgroundColor = UIColor(red: 234/255, green: 53/255, blue: 52/255, alpha: 1).cgColor 19 | layer.frame.size = CGSize(width: 6, height: 6) 20 | layer.cornerRadius = layer.frame.width/2 21 | layer.opacity = 0 //by default hidden 22 | return layer 23 | }() 24 | 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | commonInit() 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | super.init(coder: aDecoder) 32 | commonInit() 33 | } 34 | 35 | override func layoutSubviews() { 36 | super.layoutSubviews() 37 | indicatorLayer.position = CGPoint(x: -7, y: bounds.height/2) 38 | } 39 | 40 | // MARK: - Public Methods 41 | private var backingSeconds: TimeInterval = 10000 { 42 | didSet { 43 | updateLabel() 44 | } 45 | } 46 | 47 | func start() { 48 | 49 | guard secondTimer == nil else { 50 | return 51 | } 52 | 53 | secondTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] (timer) in 54 | self?.backingSeconds += 1 55 | }) 56 | secondTimer?.tolerance = 0.1 57 | 58 | indicatorTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] (timer) in 59 | self?.updateIndicator(appearDelay: 0.2) 60 | }) 61 | indicatorTimer?.tolerance = 0.1 62 | 63 | updateIndicator(appearDelay: 0) 64 | } 65 | 66 | func stop() { 67 | secondTimer?.invalidate() 68 | secondTimer = nil 69 | backingSeconds = 0 70 | updateLabel() 71 | 72 | indicatorTimer?.invalidate() 73 | indicatorTimer = nil 74 | indicatorLayer.removeAllAnimations() 75 | indicatorLayer.opacity = 0 76 | } 77 | 78 | // MARK: - Private Methods 79 | private var secondTimer: Timer? 80 | private var indicatorTimer: Timer? 81 | 82 | private func updateLabel() { 83 | 84 | /* 85 | DateComponentsFormatter, because it does not pad zero to hours component so it regurns pattern 0:00:00, we need 00:00:00 86 | */ 87 | let hours = Int(backingSeconds) / 3600 88 | let minutes = Int(backingSeconds) / 60 % 60 89 | let seconds = Int(backingSeconds) % 60 90 | text = String(format:"%02i:%02i:%02i", hours, minutes, seconds) 91 | } 92 | 93 | private func updateIndicator(appearDelay: CFTimeInterval = 0) { 94 | 95 | let disappearDelay: CFTimeInterval = 0.25 96 | 97 | let appear = appearAnimation(delay: appearDelay) 98 | let disappear = disappearAnimation(delay: appear.beginTime + appear.duration + disappearDelay) 99 | 100 | let animation = CAAnimationGroup() 101 | animation.animations = [appear, disappear] 102 | animation.duration = appear.duration + disappear.duration + appearDelay + disappearDelay 103 | animation.isRemovedOnCompletion = true 104 | 105 | indicatorLayer.add(animation, forKey: "blinkAnimationKey") 106 | } 107 | 108 | private func commonInit() { 109 | layer.addSublayer(indicatorLayer) 110 | clipsToBounds = false 111 | } 112 | 113 | private func appearAnimation(delay: CFTimeInterval = 0) -> CAAnimation { 114 | let appear = CABasicAnimation(keyPath: "opacity") 115 | appear.fromValue = indicatorLayer.presentation()?.opacity 116 | appear.toValue = 1 117 | appear.duration = 0.15 118 | appear.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 119 | appear.beginTime = delay 120 | appear.fillMode = kCAFillModeForwards 121 | return appear 122 | } 123 | 124 | private func disappearAnimation(delay: CFTimeInterval = 0) -> CAAnimation { 125 | let disappear = CABasicAnimation(keyPath: "opacity") 126 | disappear.fromValue = indicatorLayer.presentation()?.opacity 127 | disappear.toValue = 0 128 | disappear.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) 129 | disappear.beginTime = delay 130 | disappear.duration = 0.25 131 | return disappear 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/RAShutterButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAShutterButton.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import Foundation 9 | 10 | /* 11 | RAShutterButton: a rounded button that has a circle inside and is used when taking pictures. 12 | */ 13 | class RAShutterButton : UIButton { 14 | 15 | var outerBorderWidth: CGFloat = 3 16 | var innerBorderWidth: CGFloat = 1.5 17 | var pressDepthFactor: CGFloat = 0.9 18 | 19 | override var isHighlighted: Bool { 20 | didSet { setInnerLayer(tapped: isHighlighted, animated: true) } 21 | } 22 | 23 | private var innerCircleLayerInset: CGFloat { 24 | return outerBorderWidth + innerBorderWidth 25 | } 26 | 27 | private var outerCircleLayer: CALayer 28 | private var innerCircleLayer: CALayer 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | outerCircleLayer = CALayer() 32 | innerCircleLayer = CALayer() 33 | super.init(coder: aDecoder) 34 | backgroundColor = UIColor.clear 35 | layer.addSublayer(outerCircleLayer) 36 | layer.addSublayer(innerCircleLayer) 37 | 38 | CATransaction.setDisableActions(true) 39 | 40 | outerCircleLayer.backgroundColor = UIColor.clear.cgColor 41 | outerCircleLayer.cornerRadius = bounds.width/2 42 | outerCircleLayer.borderWidth = outerBorderWidth 43 | outerCircleLayer.borderColor = tintColor.cgColor 44 | 45 | innerCircleLayer.backgroundColor = tintColor.cgColor 46 | 47 | CATransaction.commit() 48 | } 49 | 50 | func setInnerLayer(tapped: Bool, animated: Bool) { 51 | 52 | if animated { 53 | let animation = CABasicAnimation() 54 | animation.keyPath = "transform.scale" 55 | 56 | if tapped { 57 | animation.fromValue = innerCircleLayer.presentation()?.value(forKeyPath: "transform.scale") 58 | animation.toValue = pressDepthFactor 59 | animation.duration = 0.25 60 | } 61 | else { 62 | animation.fromValue = pressDepthFactor 63 | animation.toValue = 1.0 64 | animation.duration = 0.25 65 | } 66 | 67 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 68 | animation.beginTime = CACurrentMediaTime() 69 | animation.fillMode = kCAFillModeForwards 70 | animation.isRemovedOnCompletion = false 71 | 72 | innerCircleLayer.add(animation, forKey: nil) 73 | } 74 | else { 75 | CATransaction.setDisableActions(true) 76 | if tapped { 77 | innerCircleLayer.setValue(pressDepthFactor, forKeyPath: "transform.scale") 78 | } 79 | else { 80 | innerCircleLayer.setValue(CGFloat(1), forKeyPath: "transform.scale") 81 | } 82 | CATransaction.commit() 83 | } 84 | } 85 | 86 | override func layoutSubviews() { 87 | super.layoutSubviews() 88 | 89 | CATransaction.setDisableActions(true) 90 | outerCircleLayer.frame = bounds 91 | innerCircleLayer.frame = bounds.insetBy(dx: innerCircleLayerInset, dy: innerCircleLayerInset) 92 | innerCircleLayer.cornerRadius = bounds.insetBy(dx: innerCircleLayerInset, dy: innerCircleLayerInset).width/2 93 | CATransaction.commit() 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /RAImagePicker/Classes/Views/RAStationaryButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RAStationaryButton.swift 3 | // RAImagePicker 4 | // 5 | // Created by Rashed Al Lahaseh on 12/1/17. 6 | // 7 | 8 | import Foundation 9 | 10 | /* 11 | RAStationaryButton: a button that keeps selected state when selected. 12 | */ 13 | class RAStationaryButton : UIButton { 14 | 15 | var unselectedTintColor: UIColor? 16 | var selectedTintColor: UIColor? 17 | 18 | open override var isSelected: Bool { 19 | get { return super.isSelected } 20 | set { setSelected(newValue, animated: false) } 21 | } 22 | 23 | open override var isHighlighted: Bool { 24 | didSet { 25 | if isHighlighted == false { 26 | setSelected(!isSelected, animated: true) 27 | } 28 | } 29 | } 30 | 31 | public func setSelected(_ selected: Bool, animated: Bool) { 32 | 33 | guard isSelected != selected else { 34 | return 35 | } 36 | 37 | super.isSelected = selected 38 | selectionDidChange(animated: animated) 39 | } 40 | 41 | open override func awakeFromNib() { 42 | super.awakeFromNib() 43 | updateTint() 44 | } 45 | 46 | // Note: Override this method to track when button's state is selected or deselected. You dont need to call super, default implementation does nothing. 47 | open func selectionDidChange(animated: Bool) { 48 | updateTint() 49 | } 50 | 51 | private func updateTint() { 52 | if isSelected { 53 | tintColor = selectedTintColor 54 | } 55 | else { 56 | tintColor = unselectedTintColor 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------