diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..fcd75ad Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index e69de29..2f72377 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,71 @@ +# OS +.DS_Store + +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata + +## Other +*.xccheckout +*.moved-aside +*.xcuserstate +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md + +# fastlane specific +fastlane/report.xml + +# deliver temporary files +fastlane/Preview.html + +# snapshot generated screenshots +fastlane/screenshots + +# scan temporary files +fastlane/test_output + +# in case someone is using AppCode +.idea diff --git a/ExampleApp/AppDelegate.swift b/ExampleApp/AppDelegate.swift index b4660f5..6d412f3 100644 --- a/ExampleApp/AppDelegate.swift +++ b/ExampleApp/AppDelegate.swift @@ -13,7 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } diff --git a/ExampleApp/Info.plist b/ExampleApp/Info.plist index 4db5bb5..b49e482 100644 --- a/ExampleApp/Info.plist +++ b/ExampleApp/Info.plist @@ -20,6 +20,12 @@ 1 LSRequiresIPhoneOS + NSCameraUsageDescription + Camera is used by Image picker when taking new photos or recording videos + NSMicrophoneUsageDescription + Microphone is used by Image picker when recording videos + NSPhotoLibraryUsageDescription + App uses access to Photos when picking images UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -34,11 +40,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSPhotoLibraryUsageDescription - App uses access to Photos when picking images - NSCameraUsageDescription - Camera is used by Image picker when taking new photos or recording videos - NSMicrophoneUsageDescription - Microphone is used by Image picker when recording videos diff --git a/ImagePicker.xcodeproj/project.pbxproj b/ImagePicker.xcodeproj/project.pbxproj index ff72333..37573e8 100644 --- a/ImagePicker.xcodeproj/project.pbxproj +++ b/ImagePicker.xcodeproj/project.pbxproj @@ -31,26 +31,19 @@ 42311FFC1F73DBBF00B1AEB4 /* VideoOuptutSampleBufferDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42311FFB1F73DBBE00B1AEB4 /* VideoOuptutSampleBufferDelegate.swift */; }; 42428A8E1F618E4200D4EB3B /* CustomImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42428A8D1F618E4100D4EB3B /* CustomImageCell.swift */; }; 425777531F98D110000824F0 /* ActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 425777511F98D10F000824F0 /* ActionCell.swift */; }; - 425777541F98D119000824F0 /* ActionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 425777521F98D10F000824F0 /* ActionCell.xib */; }; - 425BEC851F7D351A0091D008 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 425BEC841F7D351A0091D008 /* Assets.xcassets */; }; 427271701FA1D304008AC2B4 /* CarvedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4272716F1FA1D304008AC2B4 /* CarvedLabel.swift */; }; 427925F31F9636D700B6D55F /* StationaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427925F21F9636D700B6D55F /* StationaryButton.swift */; }; 427925F51F96381400B6D55F /* ShutterButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427925F41F96381400B6D55F /* ShutterButton.swift */; }; 427925F71F96388000B6D55F /* RecordButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427925F61F96388000B6D55F /* RecordButton.swift */; }; 427925FD1F963A8D00B6D55F /* VideoCameraCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427925FB1F963A8C00B6D55F /* VideoCameraCell.swift */; }; - 427925FE1F963B2800B6D55F /* VideoCameraCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 427925FC1F963A8C00B6D55F /* VideoCameraCell.xib */; }; 427926011F963E9C00B6D55F /* LivePhotoCameraCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427925FF1F963E9B00B6D55F /* LivePhotoCameraCell.swift */; }; - 427926021F96407900B6D55F /* LivePhotoCameraCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 427926001F963E9C00B6D55F /* LivePhotoCameraCell.xib */; }; 42990DBF1FA07AF7001658C4 /* RecordDurationLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42990DBE1FA07AF7001658C4 /* RecordDurationLabel.swift */; }; 429BFDAA1F68161D00029440 /* ImagePickerAssetModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429BFDA91F68161D00029440 /* ImagePickerAssetModel.swift */; }; 429CCBD02080D62F0050078B /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429CCBCF2080D62F0050078B /* AsynchronousOperation.swift */; }; 429CCBD22080D6620050078B /* CollectionViewBatchAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429CCBD12080D6620050078B /* CollectionViewBatchAnimation.swift */; }; 429CCBD42080D6AE0050078B /* CollectionViewUpdatesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429CCBD32080D6AE0050078B /* CollectionViewUpdatesCoordinator.swift */; }; 42A037E01F66C9E700534350 /* CustomVideoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 42A037DF1F66C9E700534350 /* CustomVideoCell.xib */; }; - 42B296A91FB1CC3200590260 /* ImagePickerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 42B296A81FB1CC3200590260 /* ImagePickerView.xib */; }; 42B296AB1FB1CC7400590260 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B296AA1FB1CC7400590260 /* ImagePickerView.swift */; }; - 42D097991F7BD6E200A66E33 /* UIImageEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = 42D097971F7BD6E100A66E33 /* UIImageEffects.m */; }; - 42D0979A1F7BD7C500A66E33 /* UIImageEffects.h in Headers */ = {isa = PBXBuildFile; fileRef = 42D097981F7BD6E100A66E33 /* UIImageEffects.h */; settings = {ATTRIBUTES = (Public, ); }; }; 42D0979C1F7BF6B300A66E33 /* AssetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42D0979B1F7BF6B300A66E33 /* AssetCell.swift */; }; 42D7036B1F7908B10057D557 /* CaptureSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42D7036A1F7908B00057D557 /* CaptureSettings.swift */; }; 42E736491F838DB80060E24D /* ViewController+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E736481F838DB80060E24D /* ViewController+Helpers.swift */; }; @@ -60,6 +53,13 @@ 42EDD4161F712B2A00EAD2F5 /* Miscellaneous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EDD4151F712B2A00EAD2F5 /* Miscellaneous.swift */; }; 42EDD41A1F716F6300EAD2F5 /* PhotoCaptureDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EDD4171F7164FB00EAD2F5 /* PhotoCaptureDelegate.swift */; }; 42F7D8011F7A5B72009D378A /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F7D8001F7A5B71009D378A /* Appearance.swift */; }; + 47B0FEF2210920C50027E6A6 /* UIImageEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B0FEF1210920C50027E6A6 /* UIImageEffects.swift */; }; + 47B0FEF42109221E0027E6A6 /* NoPermissionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B0FEF32109221E0027E6A6 /* NoPermissionsView.swift */; }; + 47D612D6210FC151004E1D8D /* LivePhotoCameraCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47D612D1210FC151004E1D8D /* LivePhotoCameraCell.xib */; }; + 47D612D7210FC151004E1D8D /* ActionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47D612D2210FC151004E1D8D /* ActionCell.xib */; }; + 47D612D8210FC151004E1D8D /* ImagePickerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47D612D3210FC151004E1D8D /* ImagePickerView.xib */; }; + 47D612D9210FC151004E1D8D /* VideoCameraCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47D612D4210FC151004E1D8D /* VideoCameraCell.xib */; }; + 47D612DA210FC151004E1D8D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 47D612D5210FC151004E1D8D /* Assets.xcassets */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -115,26 +115,19 @@ 42311FFB1F73DBBE00B1AEB4 /* VideoOuptutSampleBufferDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoOuptutSampleBufferDelegate.swift; sourceTree = ""; }; 42428A8D1F618E4100D4EB3B /* CustomImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomImageCell.swift; sourceTree = ""; }; 425777511F98D10F000824F0 /* ActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCell.swift; sourceTree = ""; }; - 425777521F98D10F000824F0 /* ActionCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActionCell.xib; sourceTree = ""; }; - 425BEC841F7D351A0091D008 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4272716F1FA1D304008AC2B4 /* CarvedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarvedLabel.swift; sourceTree = ""; }; 427925F21F9636D700B6D55F /* StationaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StationaryButton.swift; sourceTree = ""; }; 427925F41F96381400B6D55F /* ShutterButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShutterButton.swift; sourceTree = ""; }; 427925F61F96388000B6D55F /* RecordButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordButton.swift; sourceTree = ""; }; 427925FB1F963A8C00B6D55F /* VideoCameraCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCameraCell.swift; sourceTree = ""; }; - 427925FC1F963A8C00B6D55F /* VideoCameraCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VideoCameraCell.xib; sourceTree = ""; }; 427925FF1F963E9B00B6D55F /* LivePhotoCameraCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LivePhotoCameraCell.swift; sourceTree = ""; }; - 427926001F963E9C00B6D55F /* LivePhotoCameraCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LivePhotoCameraCell.xib; sourceTree = ""; }; 42990DBE1FA07AF7001658C4 /* RecordDurationLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordDurationLabel.swift; sourceTree = ""; }; 429BFDA91F68161D00029440 /* ImagePickerAssetModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerAssetModel.swift; sourceTree = ""; }; 429CCBCF2080D62F0050078B /* AsynchronousOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousOperation.swift; sourceTree = ""; }; 429CCBD12080D6620050078B /* CollectionViewBatchAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewBatchAnimation.swift; sourceTree = ""; }; 429CCBD32080D6AE0050078B /* CollectionViewUpdatesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewUpdatesCoordinator.swift; sourceTree = ""; }; 42A037DF1F66C9E700534350 /* CustomVideoCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CustomVideoCell.xib; sourceTree = ""; }; - 42B296A81FB1CC3200590260 /* ImagePickerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImagePickerView.xib; sourceTree = ""; }; 42B296AA1FB1CC7400590260 /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = ""; }; - 42D097971F7BD6E100A66E33 /* UIImageEffects.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIImageEffects.m; sourceTree = ""; }; - 42D097981F7BD6E100A66E33 /* UIImageEffects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIImageEffects.h; sourceTree = ""; }; 42D0979B1F7BF6B300A66E33 /* AssetCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCell.swift; sourceTree = ""; }; 42D7036A1F7908B00057D557 /* CaptureSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptureSettings.swift; sourceTree = ""; }; 42E736481F838DB80060E24D /* ViewController+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController+Helpers.swift"; sourceTree = ""; }; @@ -143,6 +136,13 @@ 42EDD4171F7164FB00EAD2F5 /* PhotoCaptureDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCaptureDelegate.swift; sourceTree = ""; }; 42EDD41B1F71727500EAD2F5 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 42F7D8001F7A5B71009D378A /* Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = ""; }; + 47B0FEF1210920C50027E6A6 /* UIImageEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageEffects.swift; sourceTree = ""; }; + 47B0FEF32109221E0027E6A6 /* NoPermissionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoPermissionsView.swift; sourceTree = ""; }; + 47D612D1210FC151004E1D8D /* LivePhotoCameraCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LivePhotoCameraCell.xib; sourceTree = ""; }; + 47D612D2210FC151004E1D8D /* ActionCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ActionCell.xib; sourceTree = ""; }; + 47D612D3210FC151004E1D8D /* ImagePickerView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ImagePickerView.xib; sourceTree = ""; }; + 47D612D4210FC151004E1D8D /* VideoCameraCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = VideoCameraCell.xib; sourceTree = ""; }; + 47D612D5210FC151004E1D8D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -201,6 +201,8 @@ 420C24171F5D82F9008935D4 /* ImagePicker */ = { isa = PBXGroup; children = ( + 47D612CF210FC151004E1D8D /* Resources */, + 47B0FEF1210920C50027E6A6 /* UIImageEffects.swift */, 420C24181F5D82F9008935D4 /* ImagePicker.h */, 42887C072080D5D200194155 /* Operations */, 42D7036F1F7909100057D557 /* Public */, @@ -208,14 +210,12 @@ 42990DBD1FA07A6C001658C4 /* Views */, 420C24351F5ED4AA008935D4 /* ImagePickerLayout.swift */, 429BFDA91F68161D00029440 /* ImagePickerAssetModel.swift */, + 47B0FEF32109221E0027E6A6 /* NoPermissionsView.swift */, 420C24271F5D925A008935D4 /* ImagePickerDataSource.swift */, 420C24331F5DA022008935D4 /* ImagePickerDelegate.swift */, 420C243E1F5EEA19008935D4 /* LayoutModel.swift */, 4214426E1F604498006BA45A /* ImagePickerSelectionPolicy.swift */, 42EDD4151F712B2A00EAD2F5 /* Miscellaneous.swift */, - 425BEC841F7D351A0091D008 /* Assets.xcassets */, - 42D097981F7BD6E100A66E33 /* UIImageEffects.h */, - 42D097971F7BD6E100A66E33 /* UIImageEffects.m */, 420C24191F5D82F9008935D4 /* Info.plist */, ); path = ImagePicker; @@ -248,19 +248,15 @@ isa = PBXGroup; children = ( 425777511F98D10F000824F0 /* ActionCell.swift */, - 425777521F98D10F000824F0 /* ActionCell.xib */, 42D0979B1F7BF6B300A66E33 /* AssetCell.swift */, 427925FF1F963E9B00B6D55F /* LivePhotoCameraCell.swift */, - 427926001F963E9C00B6D55F /* LivePhotoCameraCell.xib */, 427925F61F96388000B6D55F /* RecordButton.swift */, 42990DBE1FA07AF7001658C4 /* RecordDurationLabel.swift */, 427925F41F96381400B6D55F /* ShutterButton.swift */, 427925F21F9636D700B6D55F /* StationaryButton.swift */, 427925FB1F963A8C00B6D55F /* VideoCameraCell.swift */, - 427925FC1F963A8C00B6D55F /* VideoCameraCell.xib */, 4272716F1FA1D304008AC2B4 /* CarvedLabel.swift */, 42B296AA1FB1CC7400590260 /* ImagePickerView.swift */, - 42B296A81FB1CC3200590260 /* ImagePickerView.xib */, ); name = Views; sourceTree = ""; @@ -290,6 +286,26 @@ name = Media; sourceTree = ""; }; + 47D612CF210FC151004E1D8D /* Resources */ = { + isa = PBXGroup; + children = ( + 47D612D0210FC151004E1D8D /* XIB */, + 47D612D5210FC151004E1D8D /* Assets.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + 47D612D0210FC151004E1D8D /* XIB */ = { + isa = PBXGroup; + children = ( + 47D612D1210FC151004E1D8D /* LivePhotoCameraCell.xib */, + 47D612D2210FC151004E1D8D /* ActionCell.xib */, + 47D612D3210FC151004E1D8D /* ImagePickerView.xib */, + 47D612D4210FC151004E1D8D /* VideoCameraCell.xib */, + ); + path = XIB; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -298,7 +314,6 @@ buildActionMask = 2147483647; files = ( 42310B9C1F7506DD0096DA5D /* ImagePicker.h in Headers */, - 42D0979A1F7BD7C500A66E33 /* UIImageEffects.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -349,26 +364,26 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1200; ORGANIZATIONNAME = Inloop; TargetAttributes = { 420C23FB1F5D82C6008935D4 = { CreatedOnToolsVersion = 8.3.3; - DevelopmentTeam = VQHLE7RNUG; + DevelopmentTeam = P2PP2EM79F; LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; 420C24151F5D82F9008935D4 = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = VQHLE7RNUG; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 420C23F71F5D82C6008935D4 /* Build configuration list for PBXProject "ImagePicker" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -403,11 +418,11 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 427925FE1F963B2800B6D55F /* VideoCameraCell.xib in Resources */, - 425BEC851F7D351A0091D008 /* Assets.xcassets in Resources */, - 427926021F96407900B6D55F /* LivePhotoCameraCell.xib in Resources */, - 42B296A91FB1CC3200590260 /* ImagePickerView.xib in Resources */, - 425777541F98D119000824F0 /* ActionCell.xib in Resources */, + 47D612D8210FC151004E1D8D /* ImagePickerView.xib in Resources */, + 47D612D6210FC151004E1D8D /* LivePhotoCameraCell.xib in Resources */, + 47D612D7210FC151004E1D8D /* ActionCell.xib in Resources */, + 47D612DA210FC151004E1D8D /* Assets.xcassets in Resources */, + 47D612D9210FC151004E1D8D /* VideoCameraCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -445,9 +460,9 @@ 420C24281F5D925A008935D4 /* ImagePickerDataSource.swift in Sources */, 4214426F1F604498006BA45A /* ImagePickerSelectionPolicy.swift in Sources */, 420C243F1F5EEA19008935D4 /* LayoutModel.swift in Sources */, - 42D097991F7BD6E200A66E33 /* UIImageEffects.m in Sources */, 4217080E1F62D06D000DA508 /* CameraCollectionViewCell.swift in Sources */, 427925F51F96381400B6D55F /* ShutterButton.swift in Sources */, + 47B0FEF2210920C50027E6A6 /* UIImageEffects.swift in Sources */, 42EDD4061F71261600EAD2F5 /* AVPreviewView.swift in Sources */, 420C24411F5EEA3C008935D4 /* LayoutConfiguration.swift in Sources */, 429CCBD02080D62F0050078B /* AsynchronousOperation.swift in Sources */, @@ -456,6 +471,7 @@ 427926011F963E9C00B6D55F /* LivePhotoCameraCell.swift in Sources */, 42311FFC1F73DBBF00B1AEB4 /* VideoOuptutSampleBufferDelegate.swift in Sources */, 427271701FA1D304008AC2B4 /* CarvedLabel.swift in Sources */, + 47B0FEF42109221E0027E6A6 /* NoPermissionsView.swift in Sources */, 42F7D8011F7A5B72009D378A /* Appearance.swift in Sources */, 42B296AB1FB1CC7400590260 /* ImagePickerView.swift in Sources */, 420C24261F5D8393008935D4 /* ImagePickerController.swift in Sources */, @@ -500,6 +516,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -521,6 +538,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -545,12 +563,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -558,6 +577,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -579,6 +599,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -597,10 +618,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -610,14 +632,14 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = VQHLE7RNUG; + DEVELOPMENT_TEAM = P2PP2EM79F; INFOPLIST_FILE = ExampleApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.1; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = eu.inloop.ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = com.twof.ExampleApp; PRODUCT_NAME = "Image Picker"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -626,14 +648,14 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = VQHLE7RNUG; + DEVELOPMENT_TEAM = P2PP2EM79F; INFOPLIST_FILE = ExampleApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.1; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = eu.inloop.ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = com.twof.ExampleApp; PRODUCT_NAME = "Image Picker"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -650,15 +672,15 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ImagePicker/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.1; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.8; OTHER_SWIFT_FLAGS = "-DDEBUG"; PRODUCT_BUNDLE_IDENTIFIER = eu.inloop.ImagePicker; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -678,13 +700,13 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ImagePicker/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.1; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.8; PRODUCT_BUNDLE_IDENTIFIER = eu.inloop.ImagePicker; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; diff --git a/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/alanli.xcuserdatad/UserInterfaceState.xcuserstate b/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/alanli.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..da13bb0 Binary files /dev/null and b/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/alanli.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/newuser.xcuserdatad/UserInterfaceState.xcuserstate b/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/newuser.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..2173ba3 Binary files /dev/null and b/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/newuser.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/prestonf.xcuserdatad/UserInterfaceState.xcuserstate b/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/prestonf.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..24a92fc Binary files /dev/null and b/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/prestonf.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/rahul.xcuserdatad/UserInterfaceState.xcuserstate b/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/rahul.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..3aeda5b Binary files /dev/null and b/ImagePicker.xcodeproj/project.xcworkspace/xcuserdata/rahul.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ImagePicker.xcodeproj/xcshareddata/xcschemes/ImagePicker.xcscheme b/ImagePicker.xcodeproj/xcshareddata/xcschemes/ImagePicker.xcscheme index 16677e3..05c4a24 100644 --- a/ImagePicker.xcodeproj/xcshareddata/xcschemes/ImagePicker.xcscheme +++ b/ImagePicker.xcodeproj/xcshareddata/xcschemes/ImagePicker.xcscheme @@ -1,6 +1,6 @@ - - - - + + + + SchemeUserState + + ExampleApp.xcscheme + + orderHint + 1 + + ImagePicker.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/ImagePicker.xcodeproj/xcuserdata/newuser.xcuserdatad/xcschemes/xcschememanagement.plist b/ImagePicker.xcodeproj/xcuserdata/newuser.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..3c5a581 --- /dev/null +++ b/ImagePicker.xcodeproj/xcuserdata/newuser.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + ExampleApp.xcscheme + + orderHint + 1 + + ExampleApp.xcscheme_^#shared#^_ + + orderHint + 1 + + ImagePicker.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/ImagePicker.xcodeproj/xcuserdata/prestonf.xcuserdatad/xcschemes/xcschememanagement.plist b/ImagePicker.xcodeproj/xcuserdata/prestonf.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..bdaf9b3 --- /dev/null +++ b/ImagePicker.xcodeproj/xcuserdata/prestonf.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + ExampleApp.xcscheme_^#shared#^_ + + orderHint + 1 + + ImagePicker.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/ImagePicker.xcodeproj/xcuserdata/rahul.xcuserdatad/xcschemes/xcschememanagement.plist b/ImagePicker.xcodeproj/xcuserdata/rahul.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..d7aaf9b --- /dev/null +++ b/ImagePicker.xcodeproj/xcuserdata/rahul.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + ExampleApp.xcscheme + + orderHint + 1 + + ImagePicker.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/ImagePicker/.DS_Store b/ImagePicker/.DS_Store new file mode 100644 index 0000000..ee4db0a Binary files /dev/null and b/ImagePicker/.DS_Store differ diff --git a/ImagePicker/AVPreviewView.swift b/ImagePicker/AVPreviewView.swift old mode 100644 new mode 100755 index e432041..31bc7d6 --- a/ImagePicker/AVPreviewView.swift +++ b/ImagePicker/AVPreviewView.swift @@ -24,15 +24,15 @@ enum VideoDisplayMode { /// output from a capture session. /// final class AVPreviewView: UIView { - + deinit { log("deinit: \(String(describing: self))") } - + var previewLayer: AVCaptureVideoPreviewLayer { return layer as! AVCaptureVideoPreviewLayer } - + var session: AVCaptureSession? { get { return previewLayer.session } set { @@ -40,35 +40,35 @@ final class AVPreviewView: UIView { return } previewLayer.session = newValue - + } } - + var displayMode: VideoDisplayMode = .aspectFill { didSet { applyVideoDisplayMode() } } - + override class var layerClass: AnyClass { return AVCaptureVideoPreviewLayer.self } - + override init(frame: CGRect) { super.init(frame: frame) applyVideoDisplayMode() } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) applyVideoDisplayMode() } - + // MARK: Private Methods - - private func applyVideoDisplayMode() { - switch displayMode { - case .aspectFill: previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill - case .aspectFit: previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect - case .resize: previewLayer.videoGravity = AVLayerVideoGravity.resize + + private func applyVideoDisplayMode() { switch displayMode { + case .aspectFill: previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill + case .aspectFit: previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect + case .resize: previewLayer.videoGravity = AVLayerVideoGravity.resize } } } + diff --git a/ImagePicker/ActionCell.swift b/ImagePicker/ActionCell.swift old mode 100644 new mode 100755 index 346a1a4..d4a4969 --- a/ImagePicker/ActionCell.swift +++ b/ImagePicker/ActionCell.swift @@ -9,30 +9,38 @@ import Foundation import UIKit -final class ActionCell : UICollectionViewCell { - - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var imageView: UIImageView! - +public final class ActionCell : UICollectionViewCell { + + @IBOutlet public weak var backgroundImageView: UIImageView! + @IBOutlet public weak var titleLabel: UILabel! + @IBOutlet public weak var imageView: UIImageView! + @IBOutlet var leadingOffset: NSLayoutConstraint! @IBOutlet var trailingOffset: NSLayoutConstraint! @IBOutlet var topOffset: NSLayoutConstraint! @IBOutlet var bottomOffset: NSLayoutConstraint! - - override func awakeFromNib() { + + override public func awakeFromNib() { super.awakeFromNib() imageView.backgroundColor = UIColor.clear } - + } extension ActionCell { - + + public func setupOffsets() { + topOffset.constant = 5 + bottomOffset.constant = 5 + leadingOffset.constant = 5 + trailingOffset.constant = 5 + } + func update(withIndex index: Int, layoutConfiguration: LayoutConfiguration) { - + let layoutModel = LayoutModel(configuration: layoutConfiguration, assets: 0) let actionCount = layoutModel.numberOfItems(in: layoutConfiguration.sectionIndexForActions) - + titleLabel.textColor = UIColor.black switch index { case 0: @@ -43,10 +51,10 @@ extension ActionCell { imageView.image = UIImage(named: "button-photo-library", in: Bundle(for: type(of: self)), compatibleWith: nil) default: break } - + let isFirst = index == 0 let isLast = index == actionCount - 1 - + switch layoutConfiguration.scrollDirection { case .horizontal: topOffset.constant = isFirst ? 10 : 5 @@ -58,8 +66,10 @@ extension ActionCell { bottomOffset.constant = 5 leadingOffset.constant = isFirst ? 10 : 5 trailingOffset.constant = isLast ? 10 : 5 + @unknown default: break } - + } - + } + diff --git a/ImagePicker/Appearance.swift b/ImagePicker/Appearance.swift old mode 100644 new mode 100755 index df7c7e6..fdf141b --- a/ImagePicker/Appearance.swift +++ b/ImagePicker/Appearance.swift @@ -17,3 +17,4 @@ public class Appearance { /// public var backgroundColor: UIColor = UIColor(red: 208/255, green: 213/255, blue: 218/255, alpha: 1) } + diff --git a/ImagePicker/AssetCell.swift b/ImagePicker/AssetCell.swift old mode 100644 new mode 100755 index e3df935..af4e46f --- a/ImagePicker/AssetCell.swift +++ b/ImagePicker/AssetCell.swift @@ -13,10 +13,10 @@ import Photos /// Each image picker asset cell must conform to this protocol. /// public protocol ImagePickerAssetCell : class { - + /// This image view will be used when setting an asset's image var imageView: UIImageView! { get } - + /// This is a helper identifier that is used when properly displaying cells asynchronously var representedAssetIdentifier: String? { get set } } @@ -28,43 +28,43 @@ public protocol ImagePickerAssetCell : class { /// - selected icon when isSelected is true /// class VideoAssetCell : AssetCell { - + var durationLabel: UILabel var iconView: UIImageView var gradientView: UIImageView - + override init(frame: CGRect) { - + durationLabel = UILabel(frame: .zero) gradientView = UIImageView(frame: .zero) iconView = UIImageView(frame: .zero) - + super.init(frame: frame) - + gradientView.isHidden = true - + iconView.tintColor = UIColor.white iconView.contentMode = .center - + durationLabel.textColor = UIColor.white - durationLabel.font = UIFont.systemFont(ofSize: 12, weight: .semibold) + durationLabel.font = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight.semibold) durationLabel.textAlignment = .right contentView.addSubview(gradientView) contentView.addSubview(durationLabel) contentView.addSubview(iconView) } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func layoutSubviews() { super.layoutSubviews() - + gradientView.frame.size = CGSize(width: bounds.width, height: 40) gradientView.frame.origin = CGPoint(x: 0, y: bounds.height-40) - + let margin: CGFloat = 5 durationLabel.frame.size = CGSize(width: 50, height: 20) durationLabel.frame.origin = CGPoint( @@ -77,7 +77,7 @@ class VideoAssetCell : AssetCell { y: contentView.bounds.height - iconView.frame.height - margin ) } - + static let durationFormatter: DateComponentsFormatter = { let formatter = DateComponentsFormatter() formatter.unitsStyle = .positional @@ -85,9 +85,9 @@ class VideoAssetCell : AssetCell { formatter.zeroFormattingBehavior = .pad return formatter }() - + func update(with asset: PHAsset) { - + switch asset.mediaType { case .image: if asset.mediaSubtypes.contains(.photoLive) { @@ -111,9 +111,9 @@ class VideoAssetCell : AssetCell { durationLabel.text = VideoAssetCell.durationFormatter.string(from: asset.duration) default: break } - + } - + } /// @@ -122,12 +122,12 @@ class VideoAssetCell : AssetCell { /// default icon for selected state. /// class AssetCell : UICollectionViewCell, ImagePickerAssetCell { - + var imageView: UIImageView! = UIImageView(frame: .zero) fileprivate var selectedImageView = CheckView(frame: .zero) - + var representedAssetIdentifier: String? - + override var isSelected: Bool { didSet { selectedImageView.isHidden = !isSelected @@ -137,28 +137,28 @@ class AssetCell : UICollectionViewCell, ImagePickerAssetCell { } } } - + override init(frame: CGRect) { super.init(frame: frame) imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true contentView.addSubview(imageView) - + selectedImageView.frame = CGRect(x: 0, y: 0, width: 31, height: 31) - + contentView.addSubview(selectedImageView) selectedImageView.isHidden = true } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func prepareForReuse() { super.prepareForReuse() imageView.image = nil } - + override func layoutSubviews() { super.layoutSubviews() imageView.frame = bounds @@ -168,31 +168,32 @@ class AssetCell : UICollectionViewCell, ImagePickerAssetCell { y: margin ) } - + } private final class CheckView : UIImageView { - + var foregroundImage: UIImage? { get { return foregroundView.image } set { foregroundView.image = newValue } } - + private let foregroundView = UIImageView(frame: .zero) - + override init(frame: CGRect) { super.init(frame: frame) addSubview(foregroundView) contentMode = .center foregroundView.contentMode = .center } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func layoutSubviews() { super.layoutSubviews() foregroundView.frame = bounds } } + diff --git a/ImagePicker/AsynchronousOperation.swift b/ImagePicker/AsynchronousOperation.swift old mode 100644 new mode 100755 index 33c6760..5b3110b --- a/ImagePicker/AsynchronousOperation.swift +++ b/ImagePicker/AsynchronousOperation.swift @@ -13,7 +13,7 @@ import Foundation /// be enqueued in a OperationQueue and run serially. /// class AsynchronousOperation : Foundation.Operation { - + var stateFinished: Bool = false { willSet { willChangeValue(forKey: "isFinished") } didSet { didChangeValue(forKey: "isFinished") } @@ -22,19 +22,19 @@ class AsynchronousOperation : Foundation.Operation { willSet { willChangeValue(forKey: "isExecuting") } didSet { didChangeValue(forKey: "isExecuting") } } - + override var isFinished: Bool { return stateFinished } - + override var isExecuting: Bool { return stateExecuting } - + override var isAsynchronous: Bool { return true } - + override func main() { if isCancelled { completeOperation() @@ -44,18 +44,19 @@ class AsynchronousOperation : Foundation.Operation { execute() } } - + func execute() { fatalError("This method has to be overriden") } - + func completeOperation() { if self.stateExecuting == true { self.stateExecuting = false } - + if self.stateFinished == false { self.stateFinished = true } } } + diff --git a/ImagePicker/CameraCollectionViewCell.swift b/ImagePicker/CameraCollectionViewCell.swift old mode 100644 new mode 100755 index 69a7e8f..97ad1d4 --- a/ImagePicker/CameraCollectionViewCell.swift +++ b/ImagePicker/CameraCollectionViewCell.swift @@ -26,14 +26,14 @@ open class CameraCollectionViewCell : UICollectionViewCell { deinit { log("deinit: \(String(describing: self))") } - + /// contains video preview layer var previewView: AVPreviewView = { let view = AVPreviewView(frame: .zero) view.backgroundColor = UIColor.black return view }() - + /// /// holds static image that is above blur view to achieve nicer presentation /// - note: when capture session is interrupted, there is no input stream so @@ -44,35 +44,35 @@ open class CameraCollectionViewCell : UICollectionViewCell { view.contentMode = .scaleAspectFill return view }() - + var blurView: UIVisualEffectView? - + var isVisualEffectViewUsedForBlurring = false - + weak var delegate: CameraCollectionViewCellDelegate? - + // MARK: View Lifecycle Methods - + public override init(frame: CGRect) { super.init(frame: frame) backgroundView = previewView previewView.addSubview(imageView) } - + required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) backgroundView = previewView previewView.addSubview(imageView) } - + open override func layoutSubviews() { super.layoutSubviews() imageView.frame = previewView.bounds blurView?.frame = previewView.bounds } - + // MARK: Public Methods - + /// /// The cell can have multiple visual states based on autorization status. Use /// `updateCameraAuthorizationStatus()` func to udate UI. @@ -80,15 +80,15 @@ open class CameraCollectionViewCell : UICollectionViewCell { public internal(set) var authorizationStatus: AVAuthorizationStatus? { didSet { updateCameraAuthorizationStatus() } } - + /// /// Called each time an authorization status to camera is changed. Update your /// cell's UI based on current value of `authorizationStatus` property. /// open func updateCameraAuthorizationStatus() { - + } - + /// /// If live photos are enabled this method is called each time user captures /// a live photo. Override this method to update UI based on live view status. @@ -97,9 +97,9 @@ open class CameraCollectionViewCell : UICollectionViewCell { /// - parameter shouldAnimate: If the UI change should be animated or not. /// open func updateLivePhotoStatus(isProcessing: Bool, shouldAnimate: Bool) { - + } - + /// /// If video recording is enabled this method is called each time user starts or stops /// a recording. Override this method to update UI based on recording status. @@ -108,13 +108,13 @@ open class CameraCollectionViewCell : UICollectionViewCell { /// - parameter shouldAnimate: If the UI change should be animated or not. /// open func updateRecordingVideoStatus(isRecording: Bool, shouldAnimate: Bool) { - + } - + open func videoRecodingDidBecomeReady() { - + } - + /// /// Flips camera from front/rear or rear/front. Flip is always supplemented with /// an flip animation. @@ -124,56 +124,56 @@ open class CameraCollectionViewCell : UICollectionViewCell { @objc public func flipCamera(_ completion: (() -> Void)? = nil) { delegate?.flipCamera(completion) } - + /// /// Takes a picture /// @objc public func takePicture() { delegate?.takePicture() } - + /// /// Takes a live photo. Please note that live photos must be enabled when configuring Image Picker. /// @objc public func takeLivePhoto() { delegate?.takeLivePhoto() } - + @objc public func startVideoRecording() { delegate?.startVideoRecording() } - + @objc public func stopVideoRecording() { delegate?.stopVideoRecording() } - + // MARK: Internal Methods - + func blurIfNeeded(blurImage: UIImage?, animated: Bool, completion: ((Bool) -> Void)?) { - + var view: UIView - + if isVisualEffectViewUsedForBlurring == false { - + guard imageView.image == nil else { return } - + imageView.image = blurImage - + view = imageView } else { - + if blurView == nil { blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) previewView.addSubview(blurView!) } - + view = blurView! view.frame = previewView.bounds } - + view.alpha = 0 if animated == false { @@ -186,37 +186,37 @@ open class CameraCollectionViewCell : UICollectionViewCell { }, completion: completion) } } - + func unblurIfNeeded(unblurImage: UIImage?, animated: Bool, completion: ((Bool) -> Void)?) { - + var animationBlock: () -> () var animationCompletionBlock: (Bool) -> () - + if isVisualEffectViewUsedForBlurring == false { - + guard imageView.image != nil else { return } - + if let image = unblurImage { imageView.image = image } - + animationBlock = { self.imageView.alpha = 0 } - + animationCompletionBlock = { finished in self.imageView.image = nil completion?(finished) } } else { - + animationBlock = { self.blurView?.alpha = 0 } - + animationCompletionBlock = { finished in completion?(finished) } @@ -230,7 +230,7 @@ open class CameraCollectionViewCell : UICollectionViewCell { UIView.animate(withDuration: 0.1, delay: 0, options: .allowAnimatedContent, animations: animationBlock, completion: animationCompletionBlock) } } - + /// /// When user taps a camera cell this method is called and the result is /// used when determining whether the tap should take a photo or not. This @@ -243,6 +243,7 @@ open class CameraCollectionViewCell : UICollectionViewCell { } return false } - - + + } + diff --git a/ImagePicker/CaptureSession.swift b/ImagePicker/CaptureSession.swift old mode 100644 new mode 100755 index 68d406a..94de1fb --- a/ImagePicker/CaptureSession.swift +++ b/ImagePicker/CaptureSession.swift @@ -13,67 +13,67 @@ import UIKit /// Groups a method that informs a delegate about progress and state of photo capturing. protocol CaptureSessionPhotoCapturingDelegate : class { - + /// called as soon as the photo was taken, use this to update UI - for example show flash animation or live photo icon func captureSession(_ session: CaptureSession, willCapturePhotoWith settings: AVCapturePhotoSettings) - + /// called when captured photo is processed and ready for use func captureSession(_ session: CaptureSession, didCapturePhotoData: Data, with settings: AVCapturePhotoSettings) - + /// called when captured photo is processed and ready for use func captureSession(_ session: CaptureSession, didFailCapturingPhotoWith error: Error) - + /// called when number of processing live photos changed, see inProgressLivePhotoCapturesCount for current count func captureSessionDidChangeNumberOfProcessingLivePhotos(_ session: CaptureSession) } /// Groups a method that informs a delegate about progress and state of video recording. protocol CaptureSessionVideoRecordingDelegate : class { - + ///called when video file recording output is added to the session func captureSessionDidBecomeReadyForVideoRecording(_ session: CaptureSession) - + ///called when recording started func captureSessionDidStartVideoRecording(_ session: CaptureSession) - + ///called when cancel recording as a result of calling `cancelVideoRecording` func. func captureSessionDidCancelVideoRecording(_ session: CaptureSession) - + ///called when a recording was successfully finished func captureSessionDid(_ session: CaptureSession, didFinishVideoRecording videoURL: URL) - + ///called when a recording was finished prematurely due to a system interruption ///(empty disk, app put on bg, etc). Video is however saved on provided URL or in ///assets library if turned on. func captureSessionDid(_ session: CaptureSession, didInterruptVideoRecording videoURL: URL, reason: Error) - + ///called when a recording failed func captureSessionDid(_ session: CaptureSession, didFailVideoRecording error: Error) } protocol CaptureSessionDelegate : class { - + ///called when session is successfully configured and started running func captureSessionDidResume(_ session: CaptureSession) - + ///called when session is was manually suspended func captureSessionDidSuspend(_ session: CaptureSession) - + ///capture session was running but did fail due to any AV error reason. func captureSession(_ session: CaptureSession, didFail error: AVError) - + ///called when creating and configuring session but something failed (e.g. input or output could not be added, etc func captureSessionDidFailConfiguringSession(_ session: CaptureSession) - + ///called when user denied access to video device when prompte func captureSession(_ session: CaptureSession, authorizationStatusFailed status: AVAuthorizationStatus) - + ///Called when user grants access to video device when prompted func captureSession(_ session: CaptureSession, authorizationStatusGranted status: AVAuthorizationStatus) - + ///called when session is interrupted due to various reasons, for example when a phone call or user starts an audio using control center, etc. func captureSession(_ session: CaptureSession, wasInterrupted reason: AVCaptureSession.InterruptionReason) - + ///called when and interruption is ended and the session was automatically resumed. func captureSessionInterruptionDidEnd(_ session: CaptureSession) } @@ -82,47 +82,47 @@ protocol CaptureSessionDelegate : class { /// Manages AVCaptureSession /// final class CaptureSession : NSObject { - + deinit { log("deinit: \(String(describing: self))") } - + fileprivate enum SessionSetupResult { case success case notAuthorized case configurationFailed } - + enum SessionPresetConfiguration { case photos, livePhotos case videos } - + weak var delegate: CaptureSessionDelegate? - + let session = AVCaptureSession() var isSessionRunning = false weak var previewLayer: AVCaptureVideoPreviewLayer? - + var presetConfiguration: SessionPresetConfiguration = .photos - + /// /// Set this method to orientation that mathches UI orientation before `prepare()` /// method is called. If you need to update orientation when session is running, /// use `updateVideoOrientation()` method instead /// var videoOrientation: AVCaptureVideoOrientation = .portrait - + /// /// Updates orientaiton on video outputs /// func updateVideoOrientation(new: AVCaptureVideoOrientation) { - + videoOrientation = new - + //we need to change orientation on all outputs self.previewLayer?.connection?.videoOrientation = new - + //TODO: we have to update orientation of video data output but it's blinking a bit which is //uggly, I have no idea how to fix this //note: when I added these 2 updates into a configuration block the lag was even worse @@ -130,54 +130,55 @@ final class CaptureSession : NSObject { //when device is disconnected also video data output connection orientation is reset, so we need to set to new proper value self.videoDataOutput?.connection(with: AVMediaType.video)?.videoOrientation = new } - + } - + /// Communicate with the session and other session objects on this queue. fileprivate let sessionQueue = DispatchQueue(label: "session queue", attributes: [], target: nil) fileprivate var setupResult: SessionSetupResult = .success fileprivate var videoDeviceInput: AVCaptureDeviceInput! - fileprivate lazy var videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera, AVCaptureDevice.DeviceType.builtInDuoCamera], mediaType: AVMediaType.video, position: .unspecified) + fileprivate lazy var videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera, AVCaptureDevice.DeviceType.builtInDualCamera], mediaType: AVMediaType.video, position: .unspecified) fileprivate var videoDataOutput: AVCaptureVideoDataOutput? fileprivate let videoOutpuSampleBufferDelegate = VideoOutputSampleBufferDelegate() - + /// returns latest captured image var latestVideoBufferImage: UIImage? { return videoOutpuSampleBufferDelegate.latestImage } - + // MARK: Video Recoding - + weak var videoRecordingDelegate: CaptureSessionVideoRecordingDelegate? fileprivate var videoFileOutput: AVCaptureMovieFileOutput? fileprivate var videoCaptureDelegate: VideoCaptureDelegate? - + var isReadyForVideoRecording: Bool { return videoFileOutput != nil } var isRecordingVideo: Bool { return videoFileOutput?.isRecording ?? false } - + // MARK: Photo Capturing - + enum LivePhotoMode { + // swiftlint:disable next identifier_name case on case off } - + weak var photoCapturingDelegate: CaptureSessionPhotoCapturingDelegate? - + // this is provided by argument of capturePhoto() //fileprivate var livePhotoMode: LivePhotoMode = .off fileprivate let photoOutput = AVCapturePhotoOutput() fileprivate var inProgressPhotoCaptureDelegates = [Int64 : PhotoCaptureDelegate]() - + /// contains number of currently processing live photos fileprivate(set) var inProgressLivePhotoCapturesCount = 0 - + // MARK: Public Methods - + func prepare() { /* Check video authorization status. Video access is required and audio @@ -189,13 +190,13 @@ final class CaptureSession : NSObject { case .authorized: // The user has previously granted access to the camera. break - + case .notDetermined: /* The user has not yet been presented with the option to grant video access. We suspend the session queue to delay session setup until the access request has completed. - + Note that audio access will be implicitly requested when we create an AVCaptureDeviceInput for audio during session setup. */ @@ -211,17 +212,17 @@ final class CaptureSession : NSObject { } capturedSelf.sessionQueue.resume() }) - + default: // The user has previously denied access. setupResult = .notAuthorized } - + /* Setup the capture session. In general it is not safe to mutate an AVCaptureSession or any of its inputs, outputs, or connections from multiple threads at the same time. - + Why not do all of this on the main queue? Because AVCaptureSession.startRunning() is a blocking call which can take a long time. We dispatch session setup to the sessionQueue so @@ -231,14 +232,14 @@ final class CaptureSession : NSObject { capturedSelf.configureSession() } } - + func resume() { sessionQueue.async { - + guard self.isSessionRunning == false else { return log("capture session: warning - trying to resume already running session") } - + switch self.setupResult { case .success: // Only setup observers and start the session running if setup succeeded. @@ -247,38 +248,38 @@ final class CaptureSession : NSObject { self.isSessionRunning = self.session.isRunning // We are not calling the delegate here explicitly, because we are observing // `running` KVO on session itself. - + case .notAuthorized: log("capture session: not authorized") DispatchQueue.main.async { [weak self] in let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video) self?.delegate?.captureSession(self!, authorizationStatusFailed: status) } - + case .configurationFailed: log("capture session: configuration failed") - + DispatchQueue.main.async { [weak self] in self?.delegate?.captureSessionDidFailConfiguringSession(self!) } } } } - + func suspend() { - + guard setupResult == .success else { return } - + //we need to capture self in order to postpone deallocation while //session is properly stopped and cleaned up sessionQueue.async { [capturedSelf = self] in - + guard self.isSessionRunning == true else { return log("capture session: warning - trying to suspend non running session") } - + capturedSelf.session.stopRunning() capturedSelf.isSessionRunning = self.session.isRunning capturedSelf.removeObservers() @@ -286,9 +287,9 @@ final class CaptureSession : NSObject { //we are KVOing `isRunning` on session itself so it's called from there } } - + // MARK: Private Methods - + /// /// Cinfigures a session before it can be used, following steps are done: /// 1. adds video input @@ -297,28 +298,28 @@ final class CaptureSession : NSObject { /// 4. adds photo output (for capturing photos) /// private func configureSession() { - + guard setupResult == .success else { return } - + log("capture session: configuring - adding video input") - + session.beginConfiguration() - + switch presetConfiguration { case .livePhotos, .photos: session.sessionPreset = AVCaptureSession.Preset.photo case .videos: session.sessionPreset = AVCaptureSession.Preset.high } - + // Add video input. do { var defaultVideoDevice: AVCaptureDevice? - + // Choose the back dual camera if available, otherwise default to a wide angle camera. - if let dualCameraDevice = AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInDuoCamera, for: AVMediaType.video, position: .back) { + if let dualCameraDevice = AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInDualCamera, for: AVMediaType.video, position: .back) { defaultVideoDevice = dualCameraDevice } else if let backCameraDevice = AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInWideAngleCamera, for: AVMediaType.video, position: .back) { @@ -335,14 +336,14 @@ final class CaptureSession : NSObject { session.commitConfiguration() return } - + let videoDeviceInput = try AVCaptureDeviceInput(device: defaultVideoDevice!) - + if session.canAddInput(videoDeviceInput) { session.addInput(videoDeviceInput) - + self.videoDeviceInput = videoDeviceInput - + DispatchQueue.main.async { /* Why are we dispatching this to the main queue? @@ -367,25 +368,25 @@ final class CaptureSession : NSObject { session.commitConfiguration() return } - - + + // Add movie file output. if presetConfiguration == .videos { - + // A capture session cannot support at the same time: // - Live Photo capture and // - movie file output // - video data output // If your capture session includes an AVCaptureMovieFileOutput object, the // isLivePhotoCaptureSupported property becomes false. - + log("capture session: configuring - adding movie file input") - + let movieFileOutput = AVCaptureMovieFileOutput() if self.session.canAddOutput(movieFileOutput) { self.session.addOutput(movieFileOutput) self.videoFileOutput = movieFileOutput - + DispatchQueue.main.async { [weak self] in self?.videoRecordingDelegate?.captureSessionDidBecomeReadyForVideoRecording(self!) } @@ -397,16 +398,16 @@ final class CaptureSession : NSObject { return } } - + if presetConfiguration == .livePhotos || presetConfiguration == .videos { - + log("capture session: configuring - adding audio input") - + // Add audio input, if fails no need to fail whole configuration do { let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio) let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice!) - + if session.canAddInput(audioDeviceInput) { session.addInput(audioDeviceInput) } @@ -418,15 +419,15 @@ final class CaptureSession : NSObject { log("capture session: could not create audio device input: \(error)") } } - + if presetConfiguration == .livePhotos || presetConfiguration == .photos || presetConfiguration == .videos { // Add photo output. log("capture session: configuring - adding photo output") - + if session.canAddOutput(photoOutput) { session.addOutput(photoOutput) photoOutput.isHighResolutionCaptureEnabled = true - + //enable live photos only if we intend to use it explicitly if presetConfiguration == .livePhotos { photoOutput.isLivePhotoCaptureEnabled = photoOutput.isLivePhotoCaptureSupported @@ -443,7 +444,7 @@ final class CaptureSession : NSObject { return } } - + if presetConfiguration != .videos { // Add video data output - we use this to capture last video sample that is // used when blurring video layer - for example when capture session is suspended, changing configuration etc. @@ -453,7 +454,7 @@ final class CaptureSession : NSObject { session.addOutput(videoDataOutput!) videoDataOutput!.alwaysDiscardsLateVideoFrames = true videoDataOutput!.setSampleBufferDelegate(videoOutpuSampleBufferDelegate, queue: videoOutpuSampleBufferDelegate.processQueue) - + if let connection = videoDataOutput!.connection(with: AVMediaType.video) { connection.videoOrientation = self.videoOrientation connection.automaticallyAdjustsVideoMirroring = false @@ -463,23 +464,23 @@ final class CaptureSession : NSObject { log("capture session: warning - could not add video data output to the session") } } - + session.commitConfiguration() } - + // MARK: KVO and Notifications - + private var sessionRunningObserveContext = 0 private var addedObservers = false - + private func addObservers() { guard addedObservers == false else { return } - + session.addObserver(self, forKeyPath: "running", options: .new, context: &sessionRunningObserveContext) NotificationCenter.default.addObserver(self, selector: #selector(subjectAreaDidChange), name: Notification.Name("AVCaptureDeviceSubjectAreaDidChangeNotification"), object: videoDeviceInput.device) NotificationCenter.default.addObserver(self, selector: #selector(sessionRuntimeError), name: Notification.Name("AVCaptureSessionRuntimeErrorNotification"), object: session) - + /* A session can only run when the app is full screen. It will be interrupted in a multi-app layout, introduced in iOS 9, see also the documentation of @@ -489,25 +490,25 @@ final class CaptureSession : NSObject { */ NotificationCenter.default.addObserver(self, selector: #selector(sessionWasInterrupted), name: Notification.Name("AVCaptureSessionWasInterruptedNotification"), object: session) NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionEnded), name: Notification.Name("AVCaptureSessionInterruptionEndedNotification"), object: session) - + addedObservers = true } - + private func removeObservers() { - + guard addedObservers == true else { return } - + NotificationCenter.default.removeObserver(self) session.removeObserver(self, forKeyPath: "running", context: &sessionRunningObserveContext) - + addedObservers = false } - + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if context == &sessionRunningObserveContext { let newValue = change?[.newKey] as AnyObject? guard let isSessionRunning = newValue?.boolValue else { return } - + DispatchQueue.main.async { [capturedSelf = self] in log("capture session: is running - \(isSessionRunning)") if isSessionRunning { @@ -522,15 +523,15 @@ final class CaptureSession : NSObject { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } } - + @objc func sessionRuntimeError(notification: NSNotification) { guard let errorValue = notification.userInfo?[AVCaptureSessionErrorKey] as? NSError else { return } - + let error = AVError(_nsError: errorValue) log("capture session: runtime error: \(error)") - + /* Automatically try to restart the session running if media services were reset and the last start running succeeded. Otherwise, enable the user @@ -555,7 +556,7 @@ final class CaptureSession : NSObject { } } } - + @objc func sessionWasInterrupted(notification: NSNotification) { /* In some scenarios we want to enable the user to resume the session running. @@ -575,10 +576,10 @@ final class CaptureSession : NSObject { log("capture session: session was interrupted due to unknown reason") } } - + @objc func sessionInterruptionEnded(notification: NSNotification) { log("capture session: interruption ended") - + //this is called automatically when interruption is done and session //is automatically resumed. Delegate should know that this happened so //the UI can be updated @@ -589,38 +590,41 @@ final class CaptureSession : NSObject { } extension CaptureSession { - + @objc func subjectAreaDidChange(notification: NSNotification) { -// let devicePoint = CGPoint(x: 0.5, y: 0.5) -// focus(with: .autoFocus, exposureMode: .continuousAutoExposure, at: devicePoint, monitorSubjectAreaChange: false) + // let devicePoint = CGPoint(x: 0.5, y: 0.5) + // focus(with: .autoFocus, exposureMode: .continuousAutoExposure, at: devicePoint, monitorSubjectAreaChange: false) } - + func changeCamera(completion: (() -> Void)?) { - + guard setupResult == .success else { return log("capture session: warning - trying to change camera but capture session setup failed") } - + sessionQueue.async { [unowned self] in let currentVideoDevice = self.videoDeviceInput.device let currentPosition = currentVideoDevice.position - + let preferredPosition: AVCaptureDevice.Position let preferredDeviceType: AVCaptureDevice.DeviceType - + switch currentPosition { case .unspecified, .front: preferredPosition = .back - preferredDeviceType = AVCaptureDevice.DeviceType.builtInDuoCamera - + preferredDeviceType = AVCaptureDevice.DeviceType.builtInDualCamera + case .back: preferredPosition = .front preferredDeviceType = AVCaptureDevice.DeviceType.builtInWideAngleCamera + default: + preferredPosition = .back + preferredDeviceType = AVCaptureDevice.DeviceType.builtInDualCamera } - + let devices = self.videoDeviceDiscoverySession.devices var newVideoDevice: AVCaptureDevice? = nil - + // First, look for a device with both the preferred position and device type. Otherwise, look for a device with only the preferred position. if let device = devices.filter({ $0.position == preferredPosition && $0.deviceType == preferredDeviceType }).first { newVideoDevice = device @@ -628,33 +632,33 @@ extension CaptureSession { else if let device = devices.filter({ $0.position == preferredPosition }).first { newVideoDevice = device } - + if let videoDevice = newVideoDevice { do { let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) - + self.session.beginConfiguration() - + // Remove the existing device input first, since using the front and back camera simultaneously is not supported. self.session.removeInput(self.videoDeviceInput) - + if self.session.canAddInput(videoDeviceInput) { NotificationCenter.default.removeObserver(self, name: Notification.Name("AVCaptureDeviceSubjectAreaDidChangeNotification"), object: currentVideoDevice) NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange), name: Notification.Name("AVCaptureDeviceSubjectAreaDidChangeNotification"), object: videoDeviceInput.device) - + self.session.addInput(videoDeviceInput) self.videoDeviceInput = videoDeviceInput } else { self.session.addInput(self.videoDeviceInput); } - + if let connection = self.videoFileOutput?.connection(with: AVMediaType.video) { if connection.isVideoStabilizationSupported { connection.preferredVideoStabilizationMode = .auto } } - + /* Set Live Photo capture enabled if it is supported. When changing cameras, the `isLivePhotoCaptureEnabled` property of the AVCapturePhotoOutput gets set to NO when @@ -662,7 +666,7 @@ extension CaptureSession { added to the session, re-enable Live Photo capture on the AVCapturePhotoOutput if it is supported. */ self.photoOutput.isLivePhotoCaptureEnabled = self.photoOutput.isLivePhotoCaptureSupported && self.presetConfiguration == .livePhotos; - + // when device is disconnected: // - video data output connection orientation is reset, so we need to set to new proper value // - video mirroring is set to true if camera is front, make sure we use no mirroring @@ -674,11 +678,11 @@ extension CaptureSession { else { log("capture session: warning - video mirroring on video data output is not supported") } - + } - + self.session.commitConfiguration() - + DispatchQueue.main.async { //[unowned self] in completion?() } @@ -689,11 +693,11 @@ extension CaptureSession { } } } - + } extension CaptureSession { - + func capturePhoto(livePhotoMode: LivePhotoMode, saveToPhotoLibrary: Bool) { /* Retrieve the video preview layer's video orientation on the main queue before @@ -703,24 +707,24 @@ extension CaptureSession { guard let videoPreviewLayerOrientation = previewLayer?.connection?.videoOrientation else { return log("capture session: warning - trying to capture a photo but no preview layer is set") } - + sessionQueue.async { // Update the photo output's connection to match the video orientation of the video preview layer. if let photoOutputConnection = self.photoOutput.connection(with: AVMediaType.video) { photoOutputConnection.videoOrientation = videoPreviewLayerOrientation } - + // Capture a JPEG photo with flash set to auto and high resolution photo enabled. let photoSettings = AVCapturePhotoSettings() photoSettings.flashMode = .auto photoSettings.isHighResolutionPhotoEnabled = true - + //TODO: we dont need preview photo, we need thumbnail format, read `previewPhotoFormat` docs //photoSettings.embeddedThumbnailPhotoFormat //if photoSettings.availablePreviewPhotoPixelFormatTypes.count > 0 { // photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String : photoSettings.availablePreviewPhotoPixelFormatTypes.first!] //} - + //TODO: I dont know how it works, need to find out if #available(iOS 11.0, *) { if photoSettings.availableEmbeddedThumbnailPhotoCodecTypes.count > 0 { @@ -733,7 +737,7 @@ extension CaptureSession { ] } } - + if livePhotoMode == .on { if self.presetConfiguration == .livePhotos && self.photoOutput.isLivePhotoCaptureSupported { let livePhotoMovieFileName = NSUUID().uuidString @@ -744,7 +748,7 @@ extension CaptureSession { log("capture session: warning - trying to capture live photo but it's not supported by current configuration, capturing regular photo instead") } } - + // Use a separate object for the photo capture delegate to isolate each capture life cycle. let photoCaptureDelegate = PhotoCaptureDelegate(with: photoSettings, willCapturePhotoAnimation: { DispatchQueue.main.async { [unowned self] in @@ -763,7 +767,7 @@ extension CaptureSession { else { self.inProgressLivePhotoCapturesCount -= 1 } - + let inProgressLivePhotoCapturesCount = self.inProgressLivePhotoCapturesCount DispatchQueue.main.async { [unowned self] in if inProgressLivePhotoCapturesCount >= 0 { @@ -779,7 +783,7 @@ extension CaptureSession { self.sessionQueue.async { [unowned self] in self.inProgressPhotoCaptureDelegates[delegate.requestedPhotoSettings.uniqueID] = nil } - + DispatchQueue.main.async { if let data = delegate.photoData { self.photoCapturingDelegate?.captureSession(self, didCapturePhotoData: data, with: delegate.requestedPhotoSettings) @@ -789,9 +793,9 @@ extension CaptureSession { } } }) - + photoCaptureDelegate.savesPhotoToLibrary = saveToPhotoLibrary - + /* The Photo Output keeps a weak reference to the photo capture delegate so we store it in an array to maintain a strong reference to this object @@ -801,59 +805,59 @@ extension CaptureSession { self.photoOutput.capturePhoto(with: photoSettings, delegate: photoCaptureDelegate) } } - + } extension CaptureSession { - + func startVideoRecording(saveToPhotoLibrary: Bool) { - + guard let movieFileOutput = self.videoFileOutput else { return log("capture session: trying to record a video but no movie file output is set") } - + guard let previewLayer = self.previewLayer else { return log("capture session: trying to record a video but no preview layer is set") } - + /* Retrieve the video preview layer's video orientation on the main queue before entering the session queue. We do this to ensure UI elements are accessed on the main thread and session configuration is done on the session queue. */ let videoPreviewLayerOrientation = previewLayer.connection?.videoOrientation - + sessionQueue.async { [weak self] in - + guard let strongSelf = self else { return } - + // if already recording do nothing guard movieFileOutput.isRecording == false else { return log("capture session: trying to record a video but there is one already being recorded") } - + // update the orientation on the movie file output video connection before starting recording. let movieFileOutputConnection = strongSelf.videoFileOutput?.connection(with: AVMediaType.video) movieFileOutputConnection?.videoOrientation = videoPreviewLayerOrientation! - + // start recording to a temporary file. let outputFileName = NSUUID().uuidString let outputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(outputFileName).appendingPathExtension("mov") - + // create a recording delegate let recordingDelegate = VideoCaptureDelegate(didStart: { DispatchQueue.main.async { [weak self] in self?.videoRecordingDelegate?.captureSessionDidStartVideoRecording(self!) } }, didFinish: { (delegate) in - + // we need to remove reference to the delegate so it can be deallocated self?.sessionQueue.async { self?.videoCaptureDelegate = nil } - + DispatchQueue.main.async { [weak self] in if delegate.isBeingCancelled { self?.videoRecordingDelegate?.captureSessionDidCancelVideoRecording(self!) @@ -862,14 +866,14 @@ extension CaptureSession { self?.videoRecordingDelegate?.captureSessionDid(self!, didFinishVideoRecording: outputURL) } } - + }, didFail: { (delegate, error) in - + // we need to remove reference to the delegate so it can be deallocated self?.sessionQueue.async { self?.videoCaptureDelegate = nil } - + DispatchQueue.main.async { [weak self] in if delegate.recordingWasInterrupted { self?.videoRecordingDelegate?.captureSessionDid(self!, didInterruptVideoRecording: outputURL, reason: error) @@ -880,7 +884,7 @@ extension CaptureSession { } }) recordingDelegate.savesVideoToLibrary = saveToPhotoLibrary - + // start recording movieFileOutput.startRecording(to: outputURL, recordingDelegate: recordingDelegate) strongSelf.videoCaptureDelegate = recordingDelegate @@ -893,37 +897,41 @@ extension CaptureSession { /// - parameter cancel: if true, recorded file will be deleted and corresponding delegate method will be called. /// func stopVideoRecording(cancel: Bool = false) { - + guard let movieFileOutput = self.videoFileOutput else { return log("capture session: trying to stop a video recording but no movie file output is set") } - + sessionQueue.async { [capturedSelf = self] in - + guard movieFileOutput.isRecording else { return log("capture session: trying to stop a video recording but no recording is in progress") } - + guard let recordingDelegate = capturedSelf.videoCaptureDelegate else { fatalError("capture session: trying to stop a video recording but video capture delegate is nil") } - + recordingDelegate.isBeingCancelled = cancel movieFileOutput.stopRecording() } } - + } extension UIInterfaceOrientation { - + var captureVideoOrientation: AVCaptureVideoOrientation { switch self { case .portrait, .unknown: return .portrait case .portraitUpsideDown: return .portraitUpsideDown case .landscapeRight: return .landscapeRight case .landscapeLeft: return .landscapeLeft + @unknown default: return .portrait + } } - + } + + diff --git a/ImagePicker/CaptureSettings.swift b/ImagePicker/CaptureSettings.swift old mode 100644 new mode 100755 index f6983b6..474c1c9 --- a/ImagePicker/CaptureSettings.swift +++ b/ImagePicker/CaptureSettings.swift @@ -12,7 +12,7 @@ import Foundation /// Configure capture session using this struct. /// public struct CaptureSettings { - + public enum CameraMode { /// /// If you support only photos use this preset. Default value. @@ -22,11 +22,11 @@ public struct CaptureSettings { /// If you know you will use live photos use this preset. /// case photoAndLivePhoto - + /// If you wish to record videos or take photos. case photoAndVideo } - + /// /// Capture session uses this preset when configuring. Select a preset of /// media types you wish to support. @@ -34,7 +34,7 @@ public struct CaptureSettings { /// - note: currently you can not change preset at runtime /// public var cameraMode: CameraMode - + /// /// Return true if captured photos will be saved to photo library. Image picker /// will prompt user with request for permisssions when needed. Default value is false @@ -44,10 +44,10 @@ public struct CaptureSettings { /// live photos and videos this is always true. /// public var savesCapturedPhotosToPhotoLibrary: Bool - + let savesCapturedLivePhotosToPhotoLibrary: Bool = true let savesCapturedVideosToPhotoLibrary: Bool = true - + /// Default configuration public static var `default`: CaptureSettings { return CaptureSettings( @@ -58,7 +58,7 @@ public struct CaptureSettings { } extension CaptureSettings.CameraMode { - + /// transforms user related enum to specific internal capture session enum var captureSessionPresetConfiguration: CaptureSession.SessionPresetConfiguration { switch self { @@ -67,5 +67,5 @@ extension CaptureSettings.CameraMode { case .photoAndVideo: return .videos } } - + } diff --git a/ImagePicker/CarvedLabel.swift b/ImagePicker/CarvedLabel.swift old mode 100644 new mode 100755 index 6086400..a5eb6a1 --- a/ImagePicker/CarvedLabel.swift +++ b/ImagePicker/CarvedLabel.swift @@ -8,7 +8,7 @@ import UIKit -fileprivate typealias TextAttributes = [NSAttributedStringKey: Any] +fileprivate typealias TextAttributes = [NSAttributedString.Key: Any] /// /// A label whose transparent text is carved into solid color. @@ -24,25 +24,25 @@ final class CarvedLabel : UIView { setNeedsDisplay() } } - + var font: UIFont? { didSet { invalidateIntrinsicContentSize() setNeedsDisplay() } } - + @IBInspectable var cornerRadius: CGFloat = 0 { didSet { setNeedsDisplay() } } - + @IBInspectable var verticalInset: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() setNeedsDisplay() } } - + @IBInspectable var horizontalInset: CGFloat = 0 { didSet { invalidateIntrinsicContentSize() @@ -50,58 +50,58 @@ final class CarvedLabel : UIView { } } - + override init(frame: CGRect) { super.init(frame: frame) _ = backgroundColor isOpaque = false } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) _ = backgroundColor isOpaque = false } - + override var backgroundColor: UIColor? { get { return UIColor.clear } set { super.backgroundColor = UIColor.clear } } - + fileprivate var textAttributes: TextAttributes { - let activeFont = font ?? UIFont.systemFont(ofSize: 12, weight: .regular) + let activeFont = font ?? UIFont.systemFont(ofSize: 12, weight: UIFont.Weight.regular) return [ - NSAttributedStringKey.font: activeFont + NSAttributedString.Key.font: activeFont ] } - + fileprivate var attributedString: NSAttributedString { return NSAttributedString(string: text ?? "", attributes: textAttributes) } - + override func draw(_ rect: CGRect) { let color = tintColor! color.setFill() - + let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius) path.fill() - + guard let context = UIGraphicsGetCurrentContext(), (text?.count ?? 0) > 0 else { return } - + let attributedString = self.attributedString let stringSize = attributedString.size() - + let xOrigin: CGFloat = max(horizontalInset, (rect.width - stringSize.width)/2) let yOrigin: CGFloat = max(verticalInset, (rect.height - stringSize.height)/2) - + context.saveGState() context.setBlendMode(.destinationOut) attributedString.draw(at: CGPoint(x: xOrigin, y: yOrigin)) context.restoreGState() } - + override func sizeThatFits(_ size: CGSize) -> CGSize { let stringSize = attributedString.size() return CGSize(width: stringSize.width + horizontalInset*2, height: stringSize.height + verticalInset*2) @@ -111,3 +111,4 @@ final class CarvedLabel : UIView { return sizeThatFits(.zero) } } + diff --git a/ImagePicker/CellRegistrator.swift b/ImagePicker/CellRegistrator.swift old mode 100644 new mode 100755 index ebc19b3..f1d8ad4 --- a/ImagePicker/CellRegistrator.swift +++ b/ImagePicker/CellRegistrator.swift @@ -20,63 +20,63 @@ import Photos /// custom camera cell implementation. /// public final class CellRegistrator { - + deinit { log("deinit: \(String(describing: self))") } - + // MARK: Private Methods - + fileprivate let actionItemIdentifierPrefix = "eu.inloop.action-item.cell-id" fileprivate var actionItemNibsData: [Int: (UINib, String)]? fileprivate var actionItemClassesData: [Int: (UICollectionViewCell.Type, String)]? - + //camera item has only 1 cell so no need for identifiers fileprivate var cameraItemNib: UINib? fileprivate var cameraItemClass: UICollectionViewCell.Type? - + fileprivate let assetItemIdentifierPrefix = "eu.inloop.asset-item.cell-id" fileprivate var assetItemNibsData: [PHAssetMediaType: (UINib, String)]? fileprivate var assetItemClassesData: [PHAssetMediaType: (UICollectionViewCell.Type, String)]? - + //we use these if there is no asset media type specified fileprivate var assetItemNib: UINib? fileprivate var assetItemClass: UICollectionViewCell.Type? - + // MARK: Internal Methods - + let cellIdentifierForCameraItem = "eu.inloop.camera-item.cell-id" - + func cellIdentifier(forActionItemAt index: Int) -> String? { - + //first lets check if there is a registered cell at specified index if let index = actionItemNibsData?[index]?.1 ?? actionItemClassesData?[index]?.1 { return index } - + //if not found globaly registered return nil guard index < Int.max else { return nil } - + //lets see if there is a globally registered cell for all indexes return cellIdentifier(forActionItemAt: Int.max) } - + var hasUserRegisteredActionCell: Bool { return (actionItemNibsData?.count ?? 0) > 0 || (actionItemClassesData?.count ?? 0) > 0 } - + var cellIdentifierForAssetItems: String { return assetItemIdentifierPrefix } - + func cellIdentifier(forAsset type: PHAssetMediaType) -> String? { return assetItemNibsData?[type]?.1 ?? assetItemClassesData?[type]?.1 } - + // MARK: Public Methods - + /// /// Register a cell nib for all action items. Use this method if all action items /// have the same cell class. @@ -84,7 +84,7 @@ public final class CellRegistrator { public func registerNibForActionItems(_ nib: UINib) { register(nib: nib, forActionItemAt: Int.max) } - + /// /// Register a cell class for all action items. Use this method if all action items /// have the same cell class. @@ -92,7 +92,7 @@ public final class CellRegistrator { public func registerCellClassForActionItems(_ cellClass: UICollectionViewCell.Type) { register(cellClass: cellClass, forActionItemAt: Int.max) } - + /// /// Register a cell nib for an action item at particular index. Use this method if /// you wish to use different cells at each index. @@ -104,7 +104,7 @@ public final class CellRegistrator { let cellIdentifier = actionItemIdentifierPrefix + String(index) actionItemNibsData?[index] = (nib, cellIdentifier) } - + /// /// Register a cell class for an action item at particular index. Use this method if /// you wish to use different cells at each index. @@ -116,14 +116,14 @@ public final class CellRegistrator { let cellIdentifier = actionItemIdentifierPrefix + String(index) actionItemClassesData?[index] = (cellClass, cellIdentifier) } - + /// /// Register a cell class for camera item. /// public func registerCellClassForCameraItem(_ cellClass: CameraCollectionViewCell.Type) { cameraItemClass = cellClass } - + /// /// Register a cell nib for camera item. /// @@ -133,7 +133,7 @@ public final class CellRegistrator { public func registerNibForCameraItem(_ nib: UINib) { cameraItemNib = nib } - + /// /// Register a cell nib for asset items of specific type (image or video). /// @@ -148,7 +148,7 @@ public final class CellRegistrator { let cellIdentifier = assetItemIdentifierPrefix + String(describing: type.rawValue) assetItemNibsData?[type] = (nib, cellIdentifier) } - + /// /// Register a cell class for asset items of specific type (image or video). /// @@ -163,14 +163,14 @@ public final class CellRegistrator { let cellIdentifier = assetItemIdentifierPrefix + String(describing: type.rawValue) assetItemClassesData?[type] = (cellClass, cellIdentifier) } - + /// /// Register a cell class for all asset items types (image and video). /// public func registerCellClassForAssetItems(_ cellClass: T.Type) where T: ImagePickerAssetCell { assetItemClass = cellClass } - + /// /// Register a cell nib for all asset items types (image and video). /// @@ -182,13 +182,13 @@ public final class CellRegistrator { } extension UICollectionView { - + /// /// Used by datasource when registering all cells to the collection view. If user /// did not register custom cells, this method registers default cells /// func apply(registrator: CellRegistrator, cameraMode: CaptureSettings.CameraMode) { - + //register action items considering type //if user did not register any nib or cell, use default action cell if registrator.hasUserRegisteredActionCell == false { @@ -203,49 +203,49 @@ extension UICollectionView { register(nibsData: registrator.actionItemNibsData?.map { $1 }) register(classData: registrator.actionItemClassesData?.map { $1 }) } - + //register camera item switch (registrator.cameraItemNib, registrator.cameraItemClass) { - + case (nil, nil): //if user does not set any class or nib we have to register default cell `CameraCollectionViewCell` based on camera mode switch cameraMode { case .photo, .photoAndLivePhoto: let nib = UINib(nibName: "LivePhotoCameraCell", bundle: Bundle(for: LivePhotoCameraCell.self)) register(nib, forCellWithReuseIdentifier: registrator.cellIdentifierForCameraItem) - + case .photoAndVideo: let nib = UINib(nibName: "VideoCameraCell", bundle: Bundle(for: VideoCameraCell.self)) register(nib, forCellWithReuseIdentifier: registrator.cellIdentifierForCameraItem) } - + case (let nib, nil): register(nib, forCellWithReuseIdentifier: registrator.cellIdentifierForCameraItem) - + case (_, let cellClass): register(cellClass, forCellWithReuseIdentifier: registrator.cellIdentifierForCameraItem) } - + //register asset items considering type register(nibsData: registrator.assetItemNibsData?.map { $1 }) register(classData: registrator.assetItemClassesData?.map { $1 }) - + //register asset items regardless of specified type switch (registrator.assetItemNib, registrator.assetItemClass) { - + case (nil, nil): //if user did not register all required classes/nibs - register default cells register(VideoAssetCell.self, forCellWithReuseIdentifier: registrator.cellIdentifierForAssetItems) //fatalError("there is not registered cell class nor nib for asset items, please user appropriate register methods on `CellRegistrator`") - + case (let nib, nil): register(nib, forCellWithReuseIdentifier: registrator.cellIdentifierForAssetItems) - + case (_, let cellClass): register(cellClass, forCellWithReuseIdentifier: registrator.cellIdentifierForAssetItems) } } - + /// /// Helper func that takes nib,cellid pair and registers them on a collection view /// @@ -255,7 +255,7 @@ extension UICollectionView { register(nib, forCellWithReuseIdentifier: cellIdentifier) } } - + /// /// Helper func that takes nib,cellid pair and registers them on a collection view /// @@ -265,5 +265,6 @@ extension UICollectionView { register(cellType, forCellWithReuseIdentifier: cellIdentifier) } } - + } + diff --git a/ImagePicker/CollectionViewBatchAnimation.swift b/ImagePicker/CollectionViewBatchAnimation.swift old mode 100644 new mode 100755 index 6f8b85f..8664e9d --- a/ImagePicker/CollectionViewBatchAnimation.swift +++ b/ImagePicker/CollectionViewBatchAnimation.swift @@ -16,17 +16,17 @@ final class CollectionViewBatchAnimation : AsynchronousOperation whe private let collectionView: UICollectionView private let sectionIndex: Int private let changes: PHFetchResultChangeDetails - + init(collectionView: UICollectionView, sectionIndex: Int, changes: PHFetchResultChangeDetails) { self.collectionView = collectionView self.sectionIndex = sectionIndex self.changes = changes } - + override func execute() { // If we have incremental diffs, animate them in the collection view collectionView.performBatchUpdates({ [unowned self] in - + // For indexes to make sense, updates must be in this order: // delete, insert, reload, move if let removed = self.changes.removedIndexes, removed.isEmpty == false { @@ -46,3 +46,4 @@ final class CollectionViewBatchAnimation : AsynchronousOperation whe }) } } + diff --git a/ImagePicker/CollectionViewUpdatesCoordinator.swift b/ImagePicker/CollectionViewUpdatesCoordinator.swift old mode 100644 new mode 100755 index 3f5f4d4..0f85ab5 --- a/ImagePicker/CollectionViewUpdatesCoordinator.swift +++ b/ImagePicker/CollectionViewUpdatesCoordinator.swift @@ -18,25 +18,25 @@ final class CollectionViewUpdatesCoordinator { deinit { log("deinit: \(String(describing: self))") } - + private let collectionView: UICollectionView - + private var serialMainQueue: OperationQueue = { let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 queue.underlyingQueue = DispatchQueue.main return queue }() - + init(collectionView: UICollectionView) { self.collectionView = collectionView } - + /// Provides opportunuty to update collectionView's dataSource in underlaying queue. func performDataSourceUpdate(updates: @escaping ()->Void) { serialMainQueue.addOperation(updates) } - + /// Updates collection view. func performChanges(_ changes: PHFetchResultChangeDetails, inSection: Int) { if changes.hasIncrementalChanges { @@ -50,3 +50,4 @@ final class CollectionViewUpdatesCoordinator { } } } + diff --git a/ImagePicker/ImagePicker.h b/ImagePicker/ImagePicker.h old mode 100644 new mode 100755 index 793e0cf..25df4a5 --- a/ImagePicker/ImagePicker.h +++ b/ImagePicker/ImagePicker.h @@ -15,5 +15,3 @@ FOUNDATION_EXPORT double ImagePickerVersionNumber; FOUNDATION_EXPORT const unsigned char ImagePickerVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import - -#import "UIImageEffects.h" diff --git a/ImagePicker/ImagePickerAssetModel.swift b/ImagePicker/ImagePickerAssetModel.swift old mode 100644 new mode 100755 index 9806cf6..3d71ae6 --- a/ImagePicker/ImagePickerAssetModel.swift +++ b/ImagePicker/ImagePickerAssetModel.swift @@ -13,26 +13,26 @@ import Photos /// Model that is used when accessing an caching PHAsset objects /// final class ImagePickerAssetModel { - + deinit { log("deinit: \(String(describing: self))") } - + var fetchResult: PHFetchResult! { set { userDefinedFetchResult = newValue } get { return userDefinedFetchResult ?? defaultFetchResult } } - + lazy var imageManager = PHCachingImageManager() var thumbnailSize: CGSize? - + /// Tryies to access smart album .smartAlbumUserLibrary that should be `Camera Roll` and uses just fetchAssets as fallback private lazy var defaultFetchResult: PHFetchResult = { - + let assetsOptions = PHFetchOptions() assetsOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] assetsOptions.fetchLimit = 1000 - + let collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil) if let cameraRoll = collections.firstObject { return PHAsset.fetchAssets(in: cameraRoll, options: assetsOptions) @@ -41,51 +41,52 @@ final class ImagePickerAssetModel { return PHAsset.fetchAssets(with: assetsOptions) } }() - + private var userDefinedFetchResult: PHFetchResult? - + //will be use for caching var previousPreheatRect = CGRect.zero - + func updateCachedAssets(collectionView: UICollectionView) { - + // Paradoxly, using this precaching the scrolling of images is more laggy than if there is no precaching - + guard let thumbnailSize = thumbnailSize else { return log("asset model: update cache assets - thumbnail size is nil") } - + guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return log("asset model: update cache assets - collection view layout is not flow layout") } - + // The preheat window is twice the height of the visible rect. let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size) - + var preheatRect: CGRect - + switch layout.scrollDirection { case .vertical: - + preheatRect = visibleRect.insetBy(dx: 0, dy: -0.75 * visibleRect.height) - + // Update only if the visible area is significantly different from the last preheated area. let delta = abs(preheatRect.midY - previousPreheatRect.midY) guard delta > collectionView.bounds.height / 3 else { return } - + case .horizontal: - + preheatRect = visibleRect.insetBy(dx: -0.75 * visibleRect.width, dy: 0) - + // Update only if the visible area is significantly different from the last preheated area. let delta = abs(preheatRect.midX - previousPreheatRect.midX) guard delta > collectionView.bounds.width / 3 else { return } + @unknown default: return } - + // Compute the assets to start caching and to stop caching. let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect, layout.scrollDirection) let addedAssets = addedRects @@ -94,15 +95,16 @@ final class ImagePickerAssetModel { let removedAssets = removedRects .flatMap { rect in collectionView.indexPathsForElements(in: rect) } .map { indexPath in fetchResult.object(at: indexPath.item) } - + // Update the assets the PHCachingImageManager is caching. imageManager.startCachingImages(for: addedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) log("asset model: caching, size \(thumbnailSize), preheat rect \(preheatRect), items \(addedAssets.count)") - + imageManager.stopCachingImages(for: removedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) log("asset model: uncaching, preheat rect \(preheatRect), items \(removedAssets.count)") - + // Store the preheat rect to compare against in the future. previousPreheatRect = preheatRect } } + diff --git a/ImagePicker/ImagePickerController.swift b/ImagePicker/ImagePickerController.swift old mode 100644 new mode 100755 index 35aa72a..37c1543 --- a/ImagePicker/ImagePickerController.swift +++ b/ImagePicker/ImagePickerController.swift @@ -14,40 +14,40 @@ import Photos /// Group of methods informing what image picker is currently doing /// public protocol ImagePickerControllerDelegate : class { - + /// /// Called when user taps on an action item, index is either 0 or 1 depending which was tapped /// func imagePicker(controller: ImagePickerController, didSelectActionItemAt index: Int) - + /// /// Called when user select an asset. /// func imagePicker(controller: ImagePickerController, didSelect asset: PHAsset) - + /// /// Called when user unselect previously selected asset. /// func imagePicker(controller: ImagePickerController, didDeselect asset: PHAsset) - + /// /// Called when user takes new photo. /// func imagePicker(controller: ImagePickerController, didTake image: UIImage) - + /// /// Called when user takes new photo. /// //TODO: //func imagePicker(controller: ImagePickerController, didCaptureVideo url: UIImage) //func imagePicker(controller: ImagePickerController, didTake livePhoto: UIImage, videoUrl: UIImage) - + /// /// Called right before an action item collection view cell is displayed. Use this method /// to configure your cell. /// func imagePicker(controller: ImagePickerController, willDisplayActionItem cell: UICollectionViewCell, at index: Int) - + /// /// Called right before an asset item collection view cell is displayed. Use this method /// to configure your cell based on asset media type, subtype, etc. @@ -79,48 +79,48 @@ public protocol ImagePickerControllerDataSource : class { } open class ImagePickerController : UIViewController { - + deinit { PHPhotoLibrary.shared().unregisterChangeObserver(self) captureSession?.suspend() log("deinit: \(String(describing: self))") } - + // MARK: Public API - + /// /// Use this object to configure layout of action, camera and asset items. /// public var layoutConfiguration = LayoutConfiguration.default - + /// /// Use this to register a cell classes or nibs for each item types /// public lazy var cellRegistrator = CellRegistrator() - + /// /// Use these settings to configure how the capturing should behave /// public var captureSettings = CaptureSettings.default - + /// /// Get informed about user interaction and changes /// public weak var delegate: ImagePickerControllerDelegate? - + /// /// Provide additional data when requested by Image Picker /// public weak var dataSource: ImagePickerControllerDataSource? - + /// /// Programatically select asset. /// - public func selectAsset(at index: Int, animated: Bool, scrollPosition: UICollectionViewScrollPosition) { + public func selectAsset(at index: Int, animated: Bool, scrollPosition: UICollectionView.ScrollPosition) { let path = IndexPath(item: index, section: layoutConfiguration.sectionIndexForAssets) collectionView.selectItem(at: path, animated: animated, scrollPosition: scrollPosition) } - + /// /// Programatically deselect asset. /// @@ -128,7 +128,7 @@ open class ImagePickerController : UIViewController { let path = IndexPath(item: index, section: layoutConfiguration.sectionIndexForAssets) collectionView.deselectItem(at: path, animated: animated) } - + /// /// Programatically deselect all selected assets. /// @@ -137,20 +137,41 @@ open class ImagePickerController : UIViewController { collectionView.deselectItem(at: selectedPath, animated: animated) } } - + /// /// Access all currently selected images /// public var selectedAssets: [PHAsset] { get { let selectedIndexPaths = collectionView.indexPathsForSelectedItems ?? [] - let selectedAssets = selectedIndexPaths.flatMap { indexPath in + let selectedAssets = selectedIndexPaths.compactMap { indexPath in return asset(at: indexPath.row) } return selectedAssets } } - + + /// Access all currently selected images by name + /// With index paths + public var selectedAssetsToIndexPath: [PHAsset: IndexPath] { + get { + let selectedIndexPaths = collectionView.indexPathsForSelectedItems ?? [] + return selectedIndexPaths.reduce(into: [PHAsset: IndexPath]()) { (dict, indexPath) in + dict[asset(at: indexPath.row)] = indexPath + } + } + } + + /// Return all visible asset cells + public var visibleAssetsToIndexPath: [PHAsset: IndexPath] { + get { + let visibleIndexPaths = collectionView.indexPathsForVisibleItems + return visibleIndexPaths.reduce(into: [PHAsset: IndexPath]()) { (dict, indexPath) in + dict[asset(at: indexPath.row)] = indexPath + } + } + } + /// /// Returns an array of assets at index set. An exception will be thrown if it fails /// @@ -160,7 +181,7 @@ open class ImagePickerController : UIViewController { } return fetchResult.objects(at: indexes) } - + /// /// Returns an asset at index. If there is no asset at the index, an exception will be thrown. /// @@ -170,7 +191,7 @@ open class ImagePickerController : UIViewController { } return fetchResult.object(at: index) } - + /// /// Fetch result of assets that will be used for picking. /// @@ -178,7 +199,7 @@ open class ImagePickerController : UIViewController { /// added smart album will be used. /// public var assetsFetchResultBlock: (() -> PHFetchResult?)? - + /// /// Global appearance proxy object. Use this object to set appearance /// for all instances of Image Picker. If you wish to set an appearance @@ -187,7 +208,7 @@ open class ImagePickerController : UIViewController { public static func appearance() -> Appearance { return classAppearanceProxy } - + /// /// Instance appearance proxy object. Use this object to set appearance /// for this particular instance of Image Picker. This has precedence over @@ -199,76 +220,79 @@ open class ImagePickerController : UIViewController { } return instanceAppearanceProxy! } - + /// /// A collection view that is used for displaying content. /// public var collectionView: UICollectionView! { return imagePickerView.collectionView } - + // MARK: Private Methods - - private var collectionViewCoordinator: CollectionViewUpdatesCoordinator! - + + fileprivate var collectionViewCoordinator: CollectionViewUpdatesCoordinator! + fileprivate var imagePickerView: ImagePickerView! { - return view as! ImagePickerView + guard let imagePickerView = view as? ImagePickerView else { fatalError() } + return imagePickerView } - + fileprivate var collectionViewDataSource = ImagePickerDataSource(assetsModel: ImagePickerAssetModel()) fileprivate var collectionViewDelegate = ImagePickerDelegate() - + fileprivate var captureSession: CaptureSession? - + private func updateItemSize() { - + guard let layout = self.collectionViewDelegate.layout else { return } - + let itemsInRow = layoutConfiguration.numberOfAssetItemsInRow let scrollDirection = layoutConfiguration.scrollDirection let cellSize = layout.sizeForItem(numberOfItemsInRow: itemsInRow, preferredWidthOrHeight: nil, collectionView: collectionView, scrollDirection: scrollDirection) let scale = UIScreen.main.scale let thumbnailSize = CGSize(width: cellSize.width * scale, height: cellSize.height * scale) self.collectionViewDataSource.assetsModel.thumbnailSize = thumbnailSize - + //TODO: we need to purge all image asset caches if item size changed } - + private func updateContentInset() { if #available(iOS 11.0, *) { collectionView.contentInset.left = view.safeAreaInsets.left collectionView.contentInset.right = view.safeAreaInsets.right } } - + /// View is used when there is a need for an overlay view over whole image picker /// view hierarchy. For example when there is no permissions to photo library. private var overlayView: UIView? - + /// Reload collection view layout/data based on authorization status of photo library private func reloadData(basedOnAuthorizationStatus status: PHAuthorizationStatus) { switch status { case .authorized: collectionViewDataSource.assetsModel.fetchResult = assetsFetchResultBlock?() collectionViewDataSource.layoutModel = LayoutModel(configuration: layoutConfiguration, assets: collectionViewDataSource.assetsModel.fetchResult.count) - - case .restricted, .denied: + + case .restricted, .denied, .limited: if let view = overlayView ?? dataSource?.imagePicker(controller: self, viewForAuthorizationStatus: status), view.superview != collectionView { collectionView.backgroundView = view overlayView = view } - + case .notDetermined: PHPhotoLibrary.requestAuthorization({ (status) in DispatchQueue.main.async { self.reloadData(basedOnAuthorizationStatus: status) } }) + @unknown default: break + } } - + /// Reload camera cell based on authorization status of camera input device (video) fileprivate func reloadCameraCell(basedOnAuthorizationStatus status: AVAuthorizationStatus) { guard let cameraCell = collectionView.cameraCell(layout: layoutConfiguration) else { @@ -276,37 +300,37 @@ open class ImagePickerController : UIViewController { } cameraCell.authorizationStatus = status } - + ///appearance object for global instances static let classAppearanceProxy = Appearance() - + ///appearance object for an instance var instanceAppearanceProxy: Appearance? - + // MARK: View Lifecycle - + open override func loadView() { let nib = UINib(nibName: "ImagePickerView", bundle: Bundle(for: ImagePickerView.self)) view = nib.instantiate(withOwner: nil, options: nil)[0] as! ImagePickerView } - + open override func viewDidLoad() { super.viewDidLoad() - + //apply appearance let appearance = instanceAppearanceProxy ?? ImagePickerController.classAppearanceProxy imagePickerView.backgroundColor = appearance.backgroundColor collectionView.backgroundColor = appearance.backgroundColor - + //create animator collectionViewCoordinator = CollectionViewUpdatesCoordinator(collectionView: collectionView) - + //configure flow layout let collectionViewLayout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout collectionViewLayout.scrollDirection = layoutConfiguration.scrollDirection collectionViewLayout.minimumInteritemSpacing = layoutConfiguration.interitemSpacing collectionViewLayout.minimumLineSpacing = layoutConfiguration.interitemSpacing - + //finish configuring collection view collectionView.dataSource = self.collectionViewDataSource collectionView.delegate = self.collectionViewDelegate @@ -316,32 +340,33 @@ open class ImagePickerController : UIViewController { switch layoutConfiguration.scrollDirection { case .horizontal: collectionView.alwaysBounceHorizontal = true case .vertical: collectionView.alwaysBounceVertical = true + @unknown default: break } - + if #available(iOS 11.0, *) { collectionView.contentInsetAdjustmentBehavior = .never } - + //gesture recognizer to detect taps on a camera cell (selection is disabled) let recognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(sender:))) recognizer.cancelsTouchesInView = false view.addGestureRecognizer(recognizer) - + //apply cell registrator to collection view collectionView.apply(registrator: cellRegistrator, cameraMode: captureSettings.cameraMode) - + //connect all remaining objects as needed collectionViewDataSource.cellRegistrator = cellRegistrator collectionViewDelegate.delegate = self collectionViewDelegate.layout = ImagePickerLayout(configuration: layoutConfiguration) - + //register for photo library updates - this is needed when changing permissions to photo library //TODO: this is expensive (loading library for the first time) PHPhotoLibrary.shared().register(self) - + //determine auth satus and based on that reload UI reloadData(basedOnAuthorizationStatus: PHPhotoLibrary.authorizationStatus()) - + //configure capture session if layoutConfiguration.showsCameraItem { let session = CaptureSession() @@ -353,102 +378,102 @@ open class ImagePickerController : UIViewController { session.photoCapturingDelegate = self session.prepare() } - + } - + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateItemSize() } - + open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() updateContentInset() collectionView.collectionViewLayout.invalidateLayout() } - + //this will make sure that collection view layout is reloaded when interface rotates/changes open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - + super.viewWillTransition(to: size, with: coordinator) - + //update capture session with new interface orientation captureSession?.updateVideoOrientation(new: UIApplication.shared.statusBarOrientation.captureVideoOrientation) - + coordinator.animate(alongsideTransition: { (context) in self.updateContentInset() }) { (context) in self.updateItemSize() } - + } - + // MARK: Private Methods - + @objc private func tapGestureRecognized(sender: UIGestureRecognizer) { guard sender.state == .ended else { return } - + guard let cameraCell = collectionView.cameraCell(layout: layoutConfiguration) else { return } - + let point = sender.location(in: cameraCell) if cameraCell.touchIsCaptureEffective(point: point) { takePicture() } } - + } extension ImagePickerController: PHPhotoLibraryChangeObserver { - + public func photoLibraryDidChange(_ changeInstance: PHChange) { - + guard let fetchResult = collectionViewDataSource.assetsModel.fetchResult, let changes = changeInstance.changeDetails(for: fetchResult) else { return } - + collectionViewCoordinator.performDataSourceUpdate { [unowned self] in - + //update old fetch result with these updates self.collectionViewDataSource.assetsModel.fetchResult = changes.fetchResultAfterChanges - + //update layout model because it changed self.collectionViewDataSource.layoutModel = LayoutModel(configuration: self.layoutConfiguration, assets: self.collectionViewDataSource.assetsModel.fetchResult.count) } - + //perform update animations - collectionViewCoordinator.performChanges(changes, inSection: layoutConfiguration.sectionIndexForAssets) + collectionViewCoordinator.performChanges(changes as! PHFetchResultChangeDetails, inSection: layoutConfiguration.sectionIndexForAssets) } } extension ImagePickerController : ImagePickerDelegateDelegate { - + func imagePicker(delegate: ImagePickerDelegate, didSelectActionItemAt index: Int) { self.delegate?.imagePicker(controller: self, didSelectActionItemAt: index) } - + func imagePicker(delegate: ImagePickerDelegate, didSelectAssetItemAt index: Int) { self.delegate?.imagePicker(controller: self, didSelect: asset(at: index)) } - + func imagePicker(delegate: ImagePickerDelegate, didDeselectAssetItemAt index: Int) { self.delegate?.imagePicker(controller: self, didDeselect: asset(at: index)) } - + func imagePicker(delegate: ImagePickerDelegate, willDisplayActionCell cell: UICollectionViewCell, at index: Int) { - + if let defaultCell = cell as? ActionCell { defaultCell.update(withIndex: index, layoutConfiguration: layoutConfiguration) } self.delegate?.imagePicker(controller: self, willDisplayActionItem: cell, at: index) } - + func imagePicker(delegate: ImagePickerDelegate, willDisplayAssetCell cell: ImagePickerAssetCell, at index: Int) { let theAsset = asset(at: index) - + //if the cell is default cell provided by Image Picker, it's our responsibility //to update the cell with the asset. if let defaultCell = cell as? VideoAssetCell { @@ -456,15 +481,15 @@ extension ImagePickerController : ImagePickerDelegateDelegate { } self.delegate?.imagePicker(controller: self, willDisplayAssetItem: cell, asset: theAsset) } - + func imagePicker(delegate: ImagePickerDelegate, willDisplayCameraCell cell: CameraCollectionViewCell) { - + //setup cell if needed if cell.delegate == nil { cell.delegate = self cell.previewView.session = captureSession?.session captureSession?.previewLayer = cell.previewView.previewLayer - + //when using videos preset, we are using different technique for //blurring the cell content. If isVisualEffectViewUsedForBlurring is //true, then UIVisualEffectView is used for blurring. In other cases @@ -474,46 +499,46 @@ extension ImagePickerController : ImagePickerDelegateDelegate { if let config = captureSession?.presetConfiguration, config == .videos { cell.isVisualEffectViewUsedForBlurring = true } - + } - + //if cell is default LivePhotoCameraCell, we must update it based on camera config if let liveCameraCell = cell as? LivePhotoCameraCell { liveCameraCell.updateWithCameraMode(captureSettings.cameraMode) } - + //update live photos let inProgressLivePhotos = captureSession?.inProgressLivePhotoCapturesCount ?? 0 cell.updateLivePhotoStatus(isProcessing: inProgressLivePhotos > 0, shouldAnimate: false) - + //update video recording status let isRecordingVideo = captureSession?.isRecordingVideo ?? false cell.updateRecordingVideoStatus(isRecording: isRecordingVideo, shouldAnimate: false) - + //update authorization status if it's changed - let status = AVCaptureDevice.authorizationStatus(for: .video) + let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video) if cell.authorizationStatus != status { cell.authorizationStatus = status } - + //resume session only if not recording video if isRecordingVideo == false { captureSession?.resume() } } - + func imagePicker(delegate: ImagePickerDelegate, didEndDisplayingCameraCell cell: CameraCollectionViewCell) { - + let isRecordingVideo = captureSession?.isRecordingVideo ?? false - + //susped session only if not recording video, otherwise the recording would be stopped. if isRecordingVideo == false { captureSession?.suspend() - + // blur cell asap DispatchQueue.global(qos: .userInteractive).async { if let image = self.captureSession?.latestVideoBufferImage { - let blurred = UIImageEffects.imageByApplyingLightEffect(to: image) + let blurred = image.applyLightEffect() DispatchQueue.main.async { cell.blurIfNeeded(blurImage: blurred, animated: false, completion: nil) } @@ -526,7 +551,7 @@ extension ImagePickerController : ImagePickerDelegateDelegate { } } } - + func imagePicker(delegate: ImagePickerDelegate, didScroll scrollView: UIScrollView) { //update only if the view is visible. //TODO: precaching is not enabled for now (it's laggy need to profile) @@ -536,182 +561,183 @@ extension ImagePickerController : ImagePickerDelegateDelegate { } extension ImagePickerController : CaptureSessionDelegate { - + func captureSessionDidResume(_ session: CaptureSession) { log("did resume") unblurCellIfNeeded(animated: true) } - + func captureSessionDidSuspend(_ session: CaptureSession) { log("did suspend") blurCellIfNeeded(animated: true) } - + func captureSession(_ session: CaptureSession, didFail error: AVError) { log("did fail") } - + func captureSessionDidFailConfiguringSession(_ session: CaptureSession) { log("did fail configuring") } - + func captureSession(_ session: CaptureSession, authorizationStatusFailed status: AVAuthorizationStatus) { log("did fail authorization to camera") reloadCameraCell(basedOnAuthorizationStatus: status) } - + func captureSession(_ session: CaptureSession, authorizationStatusGranted status: AVAuthorizationStatus) { log("did grant authorization to camera") reloadCameraCell(basedOnAuthorizationStatus: status) } - + func captureSession(_ session: CaptureSession, wasInterrupted reason: AVCaptureSession.InterruptionReason) { log("interrupted") } - + func captureSessionInterruptionDidEnd(_ session: CaptureSession) { log("interruption ended") } - + private func blurCellIfNeeded(animated: Bool) { - + guard let cameraCell = collectionView.cameraCell(layout: layoutConfiguration) else { return } guard let captureSession = captureSession else { return } - + cameraCell.blurIfNeeded(blurImage: captureSession.latestVideoBufferImage, animated: animated, completion: nil) } - + private func unblurCellIfNeeded(animated: Bool) { - + guard let cameraCell = collectionView.cameraCell(layout: layoutConfiguration) else { return } - + cameraCell.unblurIfNeeded(unblurImage: nil, animated: animated, completion: nil) } - + } extension ImagePickerController : CaptureSessionPhotoCapturingDelegate { - + func captureSession(_ session: CaptureSession, didCapturePhotoData: Data, with settings: AVCapturePhotoSettings) { log("did capture photo \(settings.uniqueID)") delegate?.imagePicker(controller: self, didTake: UIImage(data: didCapturePhotoData)!) } - + func captureSession(_ session: CaptureSession, willCapturePhotoWith settings: AVCapturePhotoSettings) { log("will capture photo \(settings.uniqueID)") } - + func captureSession(_ session: CaptureSession, didFailCapturingPhotoWith error: Error) { log("did fail capturing: \(error)") } - + func captureSessionDidChangeNumberOfProcessingLivePhotos(_ session: CaptureSession) { - + guard let cameraCell = collectionView.cameraCell(layout: layoutConfiguration) else { return } - + let count = session.inProgressLivePhotoCapturesCount cameraCell.updateLivePhotoStatus(isProcessing: count > 0, shouldAnimate: true) } } extension ImagePickerController : CaptureSessionVideoRecordingDelegate { - + func captureSessionDidBecomeReadyForVideoRecording(_ session: CaptureSession) { log("ready for video recording") guard let cameraCell = collectionView.cameraCell(layout: layoutConfiguration) else { return } cameraCell.videoRecodingDidBecomeReady() } - + func captureSessionDidStartVideoRecording(_ session: CaptureSession) { log("did start video recording") updateCameraCellRecordingStatusIfNeeded(isRecording: true, animated: true) } - + func captureSessionDidCancelVideoRecording(_ session: CaptureSession) { log("did cancel video recording") updateCameraCellRecordingStatusIfNeeded(isRecording: false, animated: true) } - + func captureSessionDid(_ session: CaptureSession, didFinishVideoRecording videoURL: URL) { log("did finish video recording") updateCameraCellRecordingStatusIfNeeded(isRecording: false, animated: true) } - + func captureSessionDid(_ session: CaptureSession, didInterruptVideoRecording videoURL: URL, reason: Error) { log("did interrupt video recording, reason: \(reason)") updateCameraCellRecordingStatusIfNeeded(isRecording: false, animated: true) } - + func captureSessionDid(_ session: CaptureSession, didFailVideoRecording error: Error) { log("did fail video recording") updateCameraCellRecordingStatusIfNeeded(isRecording: false, animated: true) } - + private func updateCameraCellRecordingStatusIfNeeded(isRecording: Bool, animated: Bool) { guard let cameraCell = collectionView.cameraCell(layout: layoutConfiguration) else { return } cameraCell.updateRecordingVideoStatus(isRecording: isRecording, shouldAnimate: animated) } - + } extension ImagePickerController: CameraCollectionViewCellDelegate { - + func takePicture() { captureSession?.capturePhoto(livePhotoMode: .off, saveToPhotoLibrary: captureSettings.savesCapturedPhotosToPhotoLibrary) } - + func takeLivePhoto() { captureSession?.capturePhoto(livePhotoMode: .on, saveToPhotoLibrary: captureSettings.savesCapturedLivePhotosToPhotoLibrary) } - + func startVideoRecording() { captureSession?.startVideoRecording(saveToPhotoLibrary: captureSettings.savesCapturedVideosToPhotoLibrary) } - + func stopVideoRecording() { captureSession?.stopVideoRecording(cancel: false) } - + func flipCamera(_ completion: (() -> Void)? = nil) { - + guard let captureSession = captureSession else { return } - + guard let cameraCell = collectionView.cameraCell(layout: layoutConfiguration) else { return captureSession.changeCamera(completion: completion) } - + var image = captureSession.latestVideoBufferImage if image != nil { - image = UIImageEffects.imageByApplyingLightEffect(to: image!) + image = image!.applyLightEffect() } - + // 1. blur cell cameraCell.blurIfNeeded(blurImage: image, animated: true) { _ in - + // 2. flip camera captureSession.changeCamera(completion: { - + // 3. flip animation UIView.transition(with: cameraCell.previewView, duration: 0.25, options: [.transitionFlipFromLeft, .allowAnimatedContent], animations: nil) { (finished) in - + //set new image from buffer var image = captureSession.latestVideoBufferImage if image != nil { - image = UIImageEffects.imageByApplyingLightEffect(to: image!) + image = image!.applyLightEffect() } - + // 4. unblur cameraCell.unblurIfNeeded(unblurImage: image, animated: true, completion: { _ in completion?() }) } }) - + } } - + } + diff --git a/ImagePicker/ImagePickerDataSource.swift b/ImagePicker/ImagePickerDataSource.swift old mode 100644 new mode 100755 index 9087b47..141f338 --- a/ImagePicker/ImagePickerDataSource.swift +++ b/ImagePicker/ImagePickerDataSource.swift @@ -13,34 +13,34 @@ import Photos /// Datasource for a collection view that is used by Image Picker VC. /// final class ImagePickerDataSource : NSObject, UICollectionViewDataSource { - + deinit { log("deinit: \(String(describing: self))") } - + var layoutModel = LayoutModel.empty var cellRegistrator: CellRegistrator? var assetsModel: ImagePickerAssetModel - + init(assetsModel: ImagePickerAssetModel) { self.assetsModel = assetsModel super.init() } - + func numberOfSections(in collectionView: UICollectionView) -> Int { return layoutModel.numberOfSections } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return layoutModel.numberOfItems(in: section) } - + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - + guard let cellsRegistrator = cellRegistrator else { fatalError("cells registrator must be set at this moment") } - + //TODO: change these hardcoded section numbers to those defined in layoutModel.layoutConfiguration switch indexPath.section { case 0: @@ -48,25 +48,25 @@ final class ImagePickerDataSource : NSObject, UICollectionViewDataSource { fatalError("there is an action item at index \(indexPath.row) but no cell is registered.") } return collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath) - + case 1: let id = cellsRegistrator.cellIdentifierForCameraItem guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath) as? CameraCollectionViewCell else { fatalError("there is a camera item but no cell class `CameraCollectionViewCell` is registered.") } return cell - + case 2: - + let asset = assetsModel.fetchResult.object(at: indexPath.item) let cellId = cellsRegistrator.cellIdentifier(forAsset: asset.mediaType) ?? cellsRegistrator.cellIdentifierForAssetItems - + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as? ImagePickerAssetCell else { fatalError("asset item cell must conform to \(ImagePickerAssetCell.self) protocol") } - + let thumbnailSize = assetsModel.thumbnailSize ?? .zero - + // Request an image for the asset from the PHCachingImageManager. cell.representedAssetIdentifier = asset.localIdentifier assetsModel.imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil, resultHandler: { image, _ in @@ -76,12 +76,11 @@ final class ImagePickerDataSource : NSObject, UICollectionViewDataSource { cell.imageView.image = image } }) - + return cell as! UICollectionViewCell - + default: fatalError("only 3 sections are supported") } } - - } + diff --git a/ImagePicker/ImagePickerDelegate.swift b/ImagePicker/ImagePickerDelegate.swift old mode 100644 new mode 100755 index 3814e6d..b803063 --- a/ImagePicker/ImagePickerDelegate.swift +++ b/ImagePicker/ImagePickerDelegate.swift @@ -10,82 +10,82 @@ import Foundation /// Informs a delegate what is going on in ImagePickerDelegate protocol ImagePickerDelegateDelegate : class { - + /// Called when user selects one of action items func imagePicker(delegate: ImagePickerDelegate, didSelectActionItemAt index: Int) - + /// Called when user selects one of asset items func imagePicker(delegate: ImagePickerDelegate, didSelectAssetItemAt index: Int) - + /// Called when user deselects one of selected asset items func imagePicker(delegate: ImagePickerDelegate, didDeselectAssetItemAt index: Int) - + /// Called when action item is about to be displayed func imagePicker(delegate: ImagePickerDelegate, willDisplayActionCell cell: UICollectionViewCell, at index: Int) - + /// Called when camera item is about to be displayed func imagePicker(delegate: ImagePickerDelegate, willDisplayCameraCell cell: CameraCollectionViewCell) - + /// Called when camera item ended displaying func imagePicker(delegate: ImagePickerDelegate, didEndDisplayingCameraCell cell: CameraCollectionViewCell) - + func imagePicker(delegate: ImagePickerDelegate, willDisplayAssetCell cell: ImagePickerAssetCell, at index: Int) - + //func imagePicker(delegate: ImagePickerDelegate, didEndDisplayingAssetCell cell: ImagePickerAssetCell) func imagePicker(delegate: ImagePickerDelegate, didScroll scrollView: UIScrollView) } final class ImagePickerDelegate : NSObject, UICollectionViewDelegateFlowLayout { - + deinit { log("deinit: \(String(describing: self))") } - + var layout: ImagePickerLayout? weak var delegate: ImagePickerDelegateDelegate? - + private let selectionPolicy = ImagePickerSelectionPolicy() - + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return layout?.collectionView(collectionView, layout: collectionViewLayout, sizeForItemAt: indexPath) ?? .zero } - + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return layout?.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: section) ?? .zero } - + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.section == layout?.configuration.sectionIndexForAssets { delegate?.imagePicker(delegate: self, didSelectAssetItemAt: indexPath.row) } } - + func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { if indexPath.section == layout?.configuration.sectionIndexForAssets { delegate?.imagePicker(delegate: self, didDeselectAssetItemAt: indexPath.row) } } - + func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { guard let configuration = layout?.configuration else { return false } return selectionPolicy.shouldSelectItem(atSection: indexPath.section, layoutConfiguration: configuration) } - + func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { guard let configuration = layout?.configuration else { return false } return selectionPolicy.shouldHighlightItem(atSection: indexPath.section, layoutConfiguration: configuration) } - + func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { if indexPath.section == layout?.configuration.sectionIndexForActions { delegate?.imagePicker(delegate: self, didSelectActionItemAt: indexPath.row) } } - + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - + guard let configuration = layout?.configuration else { return } - + switch indexPath.section { case configuration.sectionIndexForActions: delegate?.imagePicker(delegate: self, willDisplayActionCell: cell, at: indexPath.row) case configuration.sectionIndexForCamera: delegate?.imagePicker(delegate: self, willDisplayCameraCell: cell as! CameraCollectionViewCell) @@ -93,25 +93,26 @@ final class ImagePickerDelegate : NSObject, UICollectionViewDelegateFlowLayout { default: fatalError("index path not supported") } } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - + guard let configuration = layout?.configuration else { return } - + switch indexPath.section { case configuration.sectionIndexForCamera: delegate?.imagePicker(delegate: self, didEndDisplayingCameraCell: cell as! CameraCollectionViewCell) case configuration.sectionIndexForActions, configuration.sectionIndexForAssets: break default: fatalError("index path not supported") } } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { delegate?.imagePicker(delegate: self, didScroll: scrollView) } - + @available(iOS 11.0, *) func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { log("XXX: \(scrollView.adjustedContentInset)") } - + } + diff --git a/ImagePicker/ImagePickerLayout.swift b/ImagePicker/ImagePickerLayout.swift old mode 100644 new mode 100755 index 115c02f..9a6fa53 --- a/ImagePicker/ImagePickerLayout.swift +++ b/ImagePicker/ImagePickerLayout.swift @@ -10,7 +10,7 @@ import Foundation /// /// A helper class that contains all code and logic when doing layout of collection -/// view cells. This is used sollely by collection view's delegate. Typically +/// view cells. This is used sollely by collection view's delegate. Typically /// this code should be part of regular subclass of UICollectionViewLayout, however, /// since we are using UICollectionViewFlowLayout we have to do this workaround. /// @@ -19,16 +19,16 @@ final class ImagePickerLayout { deinit { log("deinit: \(String(describing: self))") } - + var configuration: LayoutConfiguration - + init(configuration: LayoutConfiguration) { self.configuration = configuration } - + /// Returns size for item considering number of rows and scroll direction, if preferredWidthOrHeight is nil, square size is returned - func sizeForItem(numberOfItemsInRow: Int, preferredWidthOrHeight: CGFloat?, collectionView: UICollectionView, scrollDirection: UICollectionViewScrollDirection) -> CGSize { - + func sizeForItem(numberOfItemsInRow: Int, preferredWidthOrHeight: CGFloat?, collectionView: UICollectionView, scrollDirection: UICollectionView.ScrollDirection) -> CGSize { + switch scrollDirection { case .horizontal: var itemHeight = collectionView.frame.height @@ -36,24 +36,25 @@ final class ImagePickerLayout { itemHeight -= (CGFloat(numberOfItemsInRow) - 1) * configuration.interitemSpacing itemHeight /= CGFloat(numberOfItemsInRow) return CGSize(width: preferredWidthOrHeight ?? itemHeight, height: itemHeight) - + case .vertical: var itemWidth = collectionView.frame.width itemWidth -= (collectionView.contentInset.left + collectionView.contentInset.right) itemWidth -= (CGFloat(numberOfItemsInRow) - 1) * configuration.interitemSpacing itemWidth /= CGFloat(numberOfItemsInRow) return CGSize(width: itemWidth, height: preferredWidthOrHeight ?? itemWidth) + @unknown default: return CGSize(width: collectionView.frame.width, height: collectionView.frame.height) } } - + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - + guard let layout = collectionViewLayout as? UICollectionViewFlowLayout else { fatalError("currently only UICollectionViewFlowLayout is supported") } - + let layoutModel = LayoutModel(configuration: configuration, assets: 0) - + switch indexPath.section { case configuration.sectionIndexForActions: //this will make sure that action item is either square if there are 2 items, @@ -62,14 +63,14 @@ final class ImagePickerLayout { let ratio: CGFloat = 0.25 let width = collectionView.frame.width * ratio return sizeForItem(numberOfItemsInRow: layoutModel.numberOfItems(in: configuration.sectionIndexForActions), preferredWidthOrHeight: width, collectionView: collectionView, scrollDirection: layout.scrollDirection) - + case configuration.sectionIndexForCamera: //lets keep this ratio so camera item is a nice rectangle - + let traitCollection = collectionView.traitCollection - + var ratio: CGFloat = 160/212 - + //for iphone in landscape we need different ratio switch traitCollection.userInterfaceIdiom { case .phone: @@ -85,41 +86,42 @@ final class ImagePickerLayout { ratio = 1/ratio default: break } - + default: break } - + let widthOrHeight: CGFloat = collectionView.frame.height * ratio return sizeForItem(numberOfItemsInRow: layoutModel.numberOfItems(in: configuration.sectionIndexForCamera), preferredWidthOrHeight: widthOrHeight, collectionView: collectionView, scrollDirection: layout.scrollDirection) - + case configuration.sectionIndexForAssets: //make sure there is at least 1 item, othewise invalid layout assert(configuration.numberOfAssetItemsInRow > 0, "invalid layout - numberOfAssetItemsInRow must be > 0, check your layout configuration ") return sizeForItem(numberOfItemsInRow: configuration.numberOfAssetItemsInRow, preferredWidthOrHeight: nil, collectionView: collectionView, scrollDirection: layout.scrollDirection) - + default: fatalError("unexpected sections count") } - + } - + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { - + guard let layout = collectionViewLayout as? UICollectionViewFlowLayout else { fatalError("currently only UICollectionViewFlowLayout is supported") } - + /// helper method that creates edge insets considering scroll direction func sectionInsets(_ inset: CGFloat) -> UIEdgeInsets { switch layout.scrollDirection { case .horizontal: return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: inset) case .vertical: return UIEdgeInsets(top: 0, left: 0, bottom: inset, right: 0) + @unknown default: return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) } } - + let layoutModel = LayoutModel(configuration: configuration, assets: 0) - + switch section { case 0 where layoutModel.numberOfItems(in: section) > 0: return sectionInsets(configuration.actionSectionSpacing) @@ -129,5 +131,6 @@ final class ImagePickerLayout { return .zero } } - + } + diff --git a/ImagePicker/ImagePickerSelectionPolicy.swift b/ImagePicker/ImagePickerSelectionPolicy.swift old mode 100644 new mode 100755 index 3505b26..2592b58 --- a/ImagePicker/ImagePickerSelectionPolicy.swift +++ b/ImagePicker/ImagePickerSelectionPolicy.swift @@ -16,7 +16,7 @@ import Foundation /// camera item is untouched. /// struct ImagePickerSelectionPolicy { - + func shouldSelectItem(atSection section: Int, layoutConfiguration: LayoutConfiguration) -> Bool { switch section { case layoutConfiguration.sectionIndexForActions, layoutConfiguration.sectionIndexForCamera: @@ -25,7 +25,7 @@ struct ImagePickerSelectionPolicy { return true } } - + func shouldHighlightItem(atSection section: Int, layoutConfiguration: LayoutConfiguration) -> Bool { switch section { case layoutConfiguration.sectionIndexForCamera: @@ -34,5 +34,6 @@ struct ImagePickerSelectionPolicy { return true } } - + } + diff --git a/ImagePicker/ImagePickerView.swift b/ImagePicker/ImagePickerView.swift old mode 100644 new mode 100755 index eff2e83..19fa886 --- a/ImagePicker/ImagePickerView.swift +++ b/ImagePicker/ImagePickerView.swift @@ -9,7 +9,8 @@ import UIKit final class ImagePickerView : UIView { - + @IBOutlet weak var collectionView: UICollectionView! - + } + diff --git a/ImagePicker/Info.plist b/ImagePicker/Info.plist old mode 100644 new mode 100755 index d9b1acd..ec0cc7b --- a/ImagePicker/Info.plist +++ b/ImagePicker/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.7 + $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/ImagePicker/LayoutConfiguration.swift b/ImagePicker/LayoutConfiguration.swift old mode 100644 new mode 100755 index 4c7751f..92db429 --- a/ImagePicker/LayoutConfiguration.swift +++ b/ImagePicker/LayoutConfiguration.swift @@ -13,34 +13,34 @@ import Foundation /// collection view items. /// public struct LayoutConfiguration { - + public var showsFirstActionItem = true public var showsSecondActionItem = true - + public var showsCameraItem = true - + let showsAssetItems = true - + /// /// Scroll and layout direction /// - public var scrollDirection: UICollectionViewScrollDirection = .horizontal - + public var scrollDirection: UICollectionView.ScrollDirection = .horizontal + /// /// Defines how many image assets will be in a row. Must be > 0 /// public var numberOfAssetItemsInRow: Int = 2 - + /// /// Spacing between items within a section /// public var interitemSpacing: CGFloat = 1 - + /// /// Spacing between actions section and camera section /// public var actionSectionSpacing: CGFloat = 1 - + /// /// Spacing between camera section and assets section /// @@ -48,32 +48,33 @@ public struct LayoutConfiguration { } extension LayoutConfiguration { - + var hasAnyAction: Bool { return showsFirstActionItem || showsSecondActionItem } - + var sectionIndexForActions: Int { return 0 } - + var sectionIndexForCamera: Int { return 1 } - + var sectionIndexForAssets: Int { return 2 } - + public static var `default` = LayoutConfiguration() - + } extension UICollectionView { - + /// Helper method for convenienet access to camera cell func cameraCell(layout: LayoutConfiguration) -> CameraCollectionViewCell? { return cellForItem(at: IndexPath(row: 0, section: layout.sectionIndexForCamera)) as? CameraCollectionViewCell } - + } + diff --git a/ImagePicker/LayoutModel.swift b/ImagePicker/LayoutModel.swift old mode 100644 new mode 100755 index e3c26d4..b3b35f6 --- a/ImagePicker/LayoutModel.swift +++ b/ImagePicker/LayoutModel.swift @@ -19,9 +19,9 @@ import Foundation /// Each section can be empty. /// struct LayoutModel { - + private var sections: [Int] = [0, 0, 0] - + init(configuration: LayoutConfiguration, assets: Int) { var actionItems: Int = configuration.showsFirstActionItem ? 1 : 0 actionItems += configuration.showsSecondActionItem ? 1 : 0 @@ -29,17 +29,18 @@ struct LayoutModel { sections[configuration.sectionIndexForCamera] = configuration.showsCameraItem ? 1 : 0 sections[configuration.sectionIndexForAssets] = assets } - + var numberOfSections: Int { return sections.count } - + func numberOfItems(in section: Int) -> Int { return sections[section] } - + static var empty: LayoutModel { let emptyConfiguration = LayoutConfiguration(showsFirstActionItem: false, showsSecondActionItem: false, showsCameraItem: false, scrollDirection: .horizontal, numberOfAssetItemsInRow: 0, interitemSpacing: 0, actionSectionSpacing: 0, cameraSectionSpacing: 0) return LayoutModel(configuration: emptyConfiguration, assets: 0) } } + diff --git a/ImagePicker/LivePhotoCameraCell.swift b/ImagePicker/LivePhotoCameraCell.swift old mode 100644 new mode 100755 index 96df69c..30392ad --- a/ImagePicker/LivePhotoCameraCell.swift +++ b/ImagePicker/LivePhotoCameraCell.swift @@ -10,20 +10,20 @@ import Foundation import UIKit class LivePhotoCameraCell : CameraCollectionViewCell { - + @IBOutlet weak var snapButton: UIButton! @IBOutlet weak var enableLivePhotosButton: StationaryButton! @IBOutlet weak var liveIndicator: CarvedLabel! - + override func awakeFromNib() { super.awakeFromNib() liveIndicator.alpha = 0 liveIndicator.tintColor = UIColor(red: 245/255, green: 203/255, blue: 47/255, alpha: 1) - + enableLivePhotosButton.unselectedTintColor = UIColor.white enableLivePhotosButton.selectedTintColor = UIColor(red: 245/255, green: 203/255, blue: 47/255, alpha: 1) } - + @IBAction func snapButtonTapped(_ sender: UIButton) { if enableLivePhotosButton.isSelected { takeLivePhoto() @@ -32,11 +32,11 @@ class LivePhotoCameraCell : CameraCollectionViewCell { takePicture() } } - + @IBAction func flipButtonTapped(_ sender: UIButton) { flipCamera() } - + func updateWithCameraMode(_ mode: CaptureSettings.CameraMode) { switch mode { case .photo: @@ -49,16 +49,17 @@ class LivePhotoCameraCell : CameraCollectionViewCell { fatalError("Image Picker - unsupported camera mode for \(type(of: self))") } } - + // MARK: Override Methods - + override func updateLivePhotoStatus(isProcessing: Bool, shouldAnimate: Bool) { - + let updates: () -> Void = { self.liveIndicator.alpha = isProcessing ? 1 : 0 } - + shouldAnimate ? UIView.animate(withDuration: 0.25, animations: updates) : updates() } } + diff --git a/ImagePicker/Miscellaneous.swift b/ImagePicker/Miscellaneous.swift old mode 100644 new mode 100755 index b14319f..893323b --- a/ImagePicker/Miscellaneous.swift +++ b/ImagePicker/Miscellaneous.swift @@ -11,22 +11,22 @@ import AVFoundation func log(_ message: String) { #if DEBUG - debugPrint(message) + debugPrint(message) #endif } extension UICollectionView { - + func indexPathsForElements(in rect: CGRect) -> [IndexPath] { let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect)! let paths = allLayoutAttributes.map { $0.indexPath } return paths } - + } extension UIInterfaceOrientation : CustomDebugStringConvertible { - + public var debugDescription: String { switch self { case .unknown: return "unknown" @@ -34,15 +34,17 @@ extension UIInterfaceOrientation : CustomDebugStringConvertible { case .portraitUpsideDown: return "portrait upside down" case .landscapeRight: return "landscape right" case .landscapeLeft: return "landscape left" + @unknown default: return "unknown" } } - + } -func differencesBetweenRects(_ old: CGRect, _ new: CGRect, _ scrollDirection: UICollectionViewScrollDirection) -> (added: [CGRect], removed: [CGRect]) { +func differencesBetweenRects(_ old: CGRect, _ new: CGRect, _ scrollDirection: UICollectionView.ScrollDirection) -> (added: [CGRect], removed: [CGRect]) { switch scrollDirection { case .horizontal: return differencesBetweenRectsHorizontal(old, new) case .vertical: return differencesBetweenRectsVertical(old, new) + @unknown default: return differencesBetweenRects(old, new, scrollDirection) } } @@ -91,3 +93,4 @@ func differencesBetweenRectsHorizontal(_ old: CGRect, _ new: CGRect) -> (added: return ([new], [old]) } } + diff --git a/ImagePicker/NoPermissionsView.swift b/ImagePicker/NoPermissionsView.swift new file mode 100644 index 0000000..313c900 --- /dev/null +++ b/ImagePicker/NoPermissionsView.swift @@ -0,0 +1,83 @@ +import UIKit + +/// Redbooth Added. +/// View for showing user that access is denied and to present a settings shortcut. +public class NoPermissionsView: UIView { + + fileprivate var instructionLabel: UILabel = { + var label = UILabel(frame: .zero) + label.translatesAutoresizingMaskIntoConstraints = false + label.textAlignment = .center + label.numberOfLines = 0 + label.font = .systemFont(ofSize: 17, weight: UIFont.Weight.regular) + let grayLevel: CGFloat = 153/255 + label.textColor = UIColor(red: grayLevel, green: grayLevel, blue: grayLevel, alpha: 1.0) + return label + }() + + fileprivate var settingsButton: UIButton = { + let button = UIButton(type: .system) + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle(NSLocalizedString("Go to Settings", comment: ""), for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 20, weight: UIFont.Weight.semibold) + return button + }() + + fileprivate var containerView: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + public init(_ instructionText: String) { + super.init(frame: .zero) + instructionLabel.text = instructionText + + setupViews() + setupConstraints() + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func navigateToSettings() { + guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) + } +} + +fileprivate extension NoPermissionsView { + + func setupViews() { + backgroundColor = .white + containerView.addSubview(instructionLabel) + containerView.addSubview(settingsButton) + addSubview(containerView) + + settingsButton.addTarget(self, action: #selector(navigateToSettings), for: .touchUpInside) + } + + func setupConstraints() { + let instructionConstraints = [ + instructionLabel.leadingAnchor.constraint(equalTo: containerView.readableContentGuide.leadingAnchor), + instructionLabel.trailingAnchor.constraint(equalTo: containerView.readableContentGuide.trailingAnchor), + instructionLabel.topAnchor.constraint(equalTo: containerView.topAnchor) + ] + let buttonConstraints = [ + settingsButton.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.readableContentGuide.leadingAnchor), + settingsButton.trailingAnchor.constraint(lessThanOrEqualTo: containerView.readableContentGuide.trailingAnchor), + settingsButton.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), + settingsButton.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 30), + settingsButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) + ] + let containerConstraints = [ + containerView.centerYAnchor.constraint(equalTo: centerYAnchor), + containerView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), + containerView.trailingAnchor.constraint(lessThanOrEqualTo: readableContentGuide.trailingAnchor) + ] + NSLayoutConstraint.activate(containerConstraints) + NSLayoutConstraint.activate(instructionConstraints) + NSLayoutConstraint.activate(buttonConstraints) + } +} diff --git a/ImagePicker/PhotoCaptureDelegate.swift b/ImagePicker/PhotoCaptureDelegate.swift old mode 100644 new mode 100755 index 6eb14d2..2d5918e --- a/ImagePicker/PhotoCaptureDelegate.swift +++ b/ImagePicker/PhotoCaptureDelegate.swift @@ -1,154 +1,155 @@ /* - Copyright (C) 2016 Apple Inc. All Rights Reserved. - See LICENSE.txt for this sample’s licensing information - - Abstract: - Photo capture delegate. -*/ + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Photo capture delegate. + */ import AVFoundation import Photos final class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate { - + deinit { log("deinit: \(String(describing: self))") } - + // MARK: Public Methods - + /// set this to false if you dont wish to save taken picture to photo library var savesPhotoToLibrary = true - + /// this contains photo data when taken private(set) var photoData: Data? = nil - + private(set) var requestedPhotoSettings: AVCapturePhotoSettings - + /// not nil if error occured during capturing private(set) var processError: Error? - + // MARK: Private Methods - - private let willCapturePhotoAnimation: () -> () - private let capturingLivePhoto: (Bool) -> () - private let completed: (PhotoCaptureDelegate) -> () - private var livePhotoCompanionMovieURL: URL? = nil - - init(with requestedPhotoSettings: AVCapturePhotoSettings, willCapturePhotoAnimation: @escaping () -> (), capturingLivePhoto: @escaping (Bool) -> (), completed: @escaping (PhotoCaptureDelegate) -> ()) { - self.requestedPhotoSettings = requestedPhotoSettings - self.willCapturePhotoAnimation = willCapturePhotoAnimation - self.capturingLivePhoto = capturingLivePhoto - self.completed = completed - } - - private func didFinish() { - if let livePhotoCompanionMoviePath = livePhotoCompanionMovieURL?.path { - if FileManager.default.fileExists(atPath: livePhotoCompanionMoviePath) { - do { - try FileManager.default.removeItem(atPath: livePhotoCompanionMoviePath) - } - catch { - log("photo capture delegate: Could not remove file at url: \(livePhotoCompanionMoviePath)") - } - } - } - - completed(self) - } - + + private let willCapturePhotoAnimation: () -> () + private let capturingLivePhoto: (Bool) -> () + private let completed: (PhotoCaptureDelegate) -> () + private var livePhotoCompanionMovieURL: URL? = nil + + init(with requestedPhotoSettings: AVCapturePhotoSettings, willCapturePhotoAnimation: @escaping () -> (), capturingLivePhoto: @escaping (Bool) -> (), completed: @escaping (PhotoCaptureDelegate) -> ()) { + self.requestedPhotoSettings = requestedPhotoSettings + self.willCapturePhotoAnimation = willCapturePhotoAnimation + self.capturingLivePhoto = capturingLivePhoto + self.completed = completed + } + + private func didFinish() { + if let livePhotoCompanionMoviePath = livePhotoCompanionMovieURL?.path { + if FileManager.default.fileExists(atPath: livePhotoCompanionMoviePath) { + do { + try FileManager.default.removeItem(atPath: livePhotoCompanionMoviePath) + } + catch { + log("photo capture delegate: Could not remove file at url: \(livePhotoCompanionMoviePath)") + } + } + } + + completed(self) + } + func photoOutput(_ captureOutput: AVCapturePhotoOutput, willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) { - if resolvedSettings.livePhotoMovieDimensions.width > 0 && resolvedSettings.livePhotoMovieDimensions.height > 0 { - capturingLivePhoto(true) - } - } - + if resolvedSettings.livePhotoMovieDimensions.width > 0 && resolvedSettings.livePhotoMovieDimensions.height > 0 { + capturingLivePhoto(true) + } + } + func photoOutput(_ captureOutput: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) { - willCapturePhotoAnimation() - } + willCapturePhotoAnimation() + } // TODO: lets for now use the older deprecated method -// @available(iOS 11.0, *) -// func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { -// if let data = photo.fileDataRepresentation() { -// photoData = data -// } -// else if let error = error { -// log("photo capture delegate: error capturing photo: \(error)") -// processError = error -// return -// } -// } - + // @available(iOS 11.0, *) + // func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { + // if let data = photo.fileDataRepresentation() { + // photoData = data + // } + // else if let error = error { + // log("photo capture delegate: error capturing photo: \(error)") + // processError = error + // return + // } + // } + //this method is not called on iOS 11 if method above is implemented func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { - if let photoSampleBuffer = photoSampleBuffer { + if let photoSampleBuffer = photoSampleBuffer { photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer) - } - else if let error = error { - log("photo capture delegate: error capturing photo: \(error)") + } + else if let error = error { + log("photo capture delegate: error capturing photo: \(error)") processError = error - return - } - } - + return + } + } + func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishRecordingLivePhotoMovieForEventualFileAt outputFileURL: URL, resolvedSettings: AVCaptureResolvedPhotoSettings) { capturingLivePhoto(false) - } - + } + func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingLivePhotoToMovieFileAt outputFileURL: URL, duration: CMTime, photoDisplayTime: CMTime, resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { - if let error = error { - log("photo capture delegate: error processing live photo companion movie: \(error)") - return - } - - livePhotoCompanionMovieURL = outputFileURL - } - + if let error = error { + log("photo capture delegate: error processing live photo companion movie: \(error)") + return + } + + livePhotoCompanionMovieURL = outputFileURL + } + func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { - + if let error = error { - log("photo capture delegate: Error capturing photo: \(error)") - didFinish() - return - } - - guard let photoData = photoData else { - log("photo capture delegate: No photo data resource") - didFinish() - return - } - + log("photo capture delegate: Error capturing photo: \(error)") + didFinish() + return + } + + guard let photoData = photoData else { + log("photo capture delegate: No photo data resource") + didFinish() + return + } + guard savesPhotoToLibrary == true else { log("photo capture delegate: photo did finish without saving to photo library") didFinish() return } - - PHPhotoLibrary.requestAuthorization { [unowned self] status in - if status == .authorized { - PHPhotoLibrary.shared().performChanges({ [unowned self] in - let creationRequest = PHAssetCreationRequest.forAsset() - creationRequest.addResource(with: .photo, data: photoData, options: nil) - - if let livePhotoCompanionMovieURL = self.livePhotoCompanionMovieURL { - let livePhotoCompanionMovieFileResourceOptions = PHAssetResourceCreationOptions() - livePhotoCompanionMovieFileResourceOptions.shouldMoveFile = true - creationRequest.addResource(with: .pairedVideo, fileURL: livePhotoCompanionMovieURL, options: livePhotoCompanionMovieFileResourceOptions) - } - + + PHPhotoLibrary.requestAuthorization { [unowned self] status in + if status == .authorized { + PHPhotoLibrary.shared().performChanges({ [unowned self] in + let creationRequest = PHAssetCreationRequest.forAsset() + creationRequest.addResource(with: .photo, data: photoData, options: nil) + + if let livePhotoCompanionMovieURL = self.livePhotoCompanionMovieURL { + let livePhotoCompanionMovieFileResourceOptions = PHAssetResourceCreationOptions() + livePhotoCompanionMovieFileResourceOptions.shouldMoveFile = true + creationRequest.addResource(with: .pairedVideo, fileURL: livePhotoCompanionMovieURL, options: livePhotoCompanionMovieFileResourceOptions) + } + }, completionHandler: { [unowned self] success, error in - if let error = error { - log("photo capture delegate: Error occurered while saving photo to photo library: \(error)") - } - - self.didFinish() - } - ) - } - else { - self.didFinish() - } - } - } + if let error = error { + log("photo capture delegate: Error occurered while saving photo to photo library: \(error)") + } + + self.didFinish() + } + ) + } + else { + self.didFinish() + } + } + } } + diff --git a/ImagePicker/RecordButton.swift b/ImagePicker/RecordButton.swift old mode 100644 new mode 100755 index 236d1be..cfb529d --- a/ImagePicker/RecordButton.swift +++ b/ImagePicker/RecordButton.swift @@ -13,11 +13,11 @@ import Foundation /// 3 states - initial, pressed, recording. /// class RecordVideoButton : StationaryButton { - + var outerBorderWidth: CGFloat = 3 { didSet { setNeedsUpdateCircleLayers() } } var innerBorderWidth: CGFloat = 1.5 { didSet { setNeedsUpdateCircleLayers() } } var pressDepthFactor: CGFloat = 0.9 { didSet { setNeedsUpdateCircleLayers() } } - + override var isHighlighted: Bool { get { return super.isHighlighted } set { @@ -27,10 +27,10 @@ class RecordVideoButton : StationaryButton { super.isHighlighted = newValue } } - + override func selectionDidChange(animated: Bool) { super.selectionDidChange(animated: animated) - + if isSelected { updateCircleLayers(state: .recording, animated: animated) } @@ -38,23 +38,23 @@ class RecordVideoButton : StationaryButton { updateCircleLayers(state: .initial, animated: animated) } } - + private var innerCircleLayerInset: CGFloat { return outerBorderWidth + innerBorderWidth } - + private var needsUpdateCircleLayers = true private var outerCircleLayer: CALayer private var innerCircleLayer: CALayer - + private enum LayersState: String { case initial case pressed case recording } - + private var layersState: LayersState = .initial - + required init?(coder aDecoder: NSCoder) { outerCircleLayer = CALayer() innerCircleLayer = CALayer() @@ -63,17 +63,17 @@ class RecordVideoButton : StationaryButton { layer.addSublayer(outerCircleLayer) layer.addSublayer(innerCircleLayer) CATransaction.setDisableActions(true) - + outerCircleLayer.backgroundColor = UIColor.clear.cgColor outerCircleLayer.cornerRadius = bounds.width/2 outerCircleLayer.borderWidth = outerBorderWidth outerCircleLayer.borderColor = tintColor.cgColor - + innerCircleLayer.backgroundColor = UIColor.red.cgColor - + CATransaction.commit() } - + override func layoutSubviews() { super.layoutSubviews() if needsUpdateCircleLayers { @@ -85,17 +85,17 @@ class RecordVideoButton : StationaryButton { CATransaction.commit() } } - + private func setNeedsUpdateCircleLayers() { needsUpdateCircleLayers = true setNeedsLayout() } - + private func updateCircleLayers(state: LayersState, animated: Bool) { guard layersState != state else { return } - + layersState = state - + switch layersState { case .initial: setInnerLayer(recording: false, animated: animated) @@ -105,9 +105,9 @@ class RecordVideoButton : StationaryButton { setInnerLayer(recording: true, animated: animated) } } - + private func setInnerLayerPressed(animated: Bool) { - + if animated { innerCircleLayer.add(transformAnimation(to: pressDepthFactor, duration: 0.25), forKey: nil) } @@ -117,9 +117,9 @@ class RecordVideoButton : StationaryButton { CATransaction.commit() } } - + private func setInnerLayer(recording: Bool, animated: Bool) { - + if recording { innerCircleLayer.add(transformAnimation(to: 0.5, duration: 0.15), forKey: nil) innerCircleLayer.cornerRadius = 8 @@ -128,20 +128,21 @@ class RecordVideoButton : StationaryButton { innerCircleLayer.add(transformAnimation(to: 1, duration: 0.25), forKey: nil) innerCircleLayer.cornerRadius = bounds.insetBy(dx: innerCircleLayerInset, dy: innerCircleLayerInset).width/2 } - + } - + private func transformAnimation(to value: CGFloat, duration: CFTimeInterval) -> CAAnimation { let animation = CABasicAnimation() animation.keyPath = "transform.scale" animation.fromValue = innerCircleLayer.presentation()?.value(forKeyPath: "transform.scale") animation.toValue = value animation.duration = duration - animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) animation.beginTime = CACurrentMediaTime() - animation.fillMode = kCAFillModeForwards + animation.fillMode = CAMediaTimingFillMode.forwards animation.isRemovedOnCompletion = false return animation } - + } + diff --git a/ImagePicker/RecordDurationLabel.swift b/ImagePicker/RecordDurationLabel.swift old mode 100644 new mode 100755 index da32bb1..8f85787 --- a/ImagePicker/RecordDurationLabel.swift +++ b/ImagePicker/RecordDurationLabel.swift @@ -13,7 +13,7 @@ import UIKit /// duration in general. /// final class RecordDurationLabel : UILabel { - + private var indicatorLayer: CALayer = { let layer = CALayer() layer.masksToBounds = true @@ -23,22 +23,22 @@ final class RecordDurationLabel : UILabel { layer.opacity = 0 //by default hidden return layer }() - + override init(frame: CGRect) { super.init(frame: frame) commonInit() } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } - + override func layoutSubviews() { super.layoutSubviews() indicatorLayer.position = CGPoint(x: -7, y: bounds.height/2) } - + // MARK: Public Methods private var backingSeconds: TimeInterval = 10000 { @@ -46,45 +46,45 @@ final class RecordDurationLabel : UILabel { updateLabel() } } - + func start() { - + guard secondTimer == nil else { return } - + secondTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] (timer) in self?.backingSeconds += 1 }) secondTimer?.tolerance = 0.1 - + indicatorTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] (timer) in self?.updateIndicator(appearDelay: 0.2) }) indicatorTimer?.tolerance = 0.1 - + updateIndicator(appearDelay: 0) } - + func stop() { secondTimer?.invalidate() secondTimer = nil backingSeconds = 0 updateLabel() - + indicatorTimer?.invalidate() indicatorTimer = nil indicatorLayer.removeAllAnimations() indicatorLayer.opacity = 0 } - + // MARK: Private Methods - + private var secondTimer: Timer? private var indicatorTimer: Timer? - + private func updateLabel() { - + //we are not using DateComponentsFormatter because it does not pad zero to hours component //so it regurns pattern 0:00:00, we need 00:00:00 let hours = Int(backingSeconds) / 3600 @@ -92,46 +92,47 @@ final class RecordDurationLabel : UILabel { let seconds = Int(backingSeconds) % 60 text = String(format:"%02i:%02i:%02i", hours, minutes, seconds) } - + private func updateIndicator(appearDelay: CFTimeInterval = 0) { let disappearDelay: CFTimeInterval = 0.25 - + let appear = appearAnimation(delay: appearDelay) let disappear = disappearAnimation(delay: appear.beginTime + appear.duration + disappearDelay) - + let animation = CAAnimationGroup() animation.animations = [appear, disappear] animation.duration = appear.duration + disappear.duration + appearDelay + disappearDelay animation.isRemovedOnCompletion = true - + indicatorLayer.add(animation, forKey: "blinkAnimationKey") } - + private func commonInit() { layer.addSublayer(indicatorLayer) clipsToBounds = false } - + private func appearAnimation(delay: CFTimeInterval = 0) -> CAAnimation { let appear = CABasicAnimation(keyPath: "opacity") appear.fromValue = indicatorLayer.presentation()?.opacity appear.toValue = 1 appear.duration = 0.15 - appear.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + appear.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) appear.beginTime = delay - appear.fillMode = kCAFillModeForwards + appear.fillMode = CAMediaTimingFillMode.forwards return appear } - + private func disappearAnimation(delay: CFTimeInterval = 0) -> CAAnimation { let disappear = CABasicAnimation(keyPath: "opacity") disappear.fromValue = indicatorLayer.presentation()?.opacity disappear.toValue = 0 - disappear.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + disappear.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) disappear.beginTime = delay disappear.duration = 0.25 return disappear } - + } + diff --git a/ImagePicker/Resources/.DS_Store b/ImagePicker/Resources/.DS_Store new file mode 100644 index 0000000..06945fc Binary files /dev/null and b/ImagePicker/Resources/.DS_Store differ diff --git a/ImagePicker/Assets.xcassets/Contents.json b/ImagePicker/Resources/Assets.xcassets/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/Contents.json rename to ImagePicker/Resources/Assets.xcassets/Contents.json diff --git a/ImagePicker/Assets.xcassets/background-rounded.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/background-rounded.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/background-rounded.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/background-rounded.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/background-rounded.imageset/background-rounded.pdf b/ImagePicker/Resources/Assets.xcassets/background-rounded.imageset/background-rounded.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/background-rounded.imageset/background-rounded.pdf rename to ImagePicker/Resources/Assets.xcassets/background-rounded.imageset/background-rounded.pdf diff --git a/ImagePicker/Assets.xcassets/button-camera.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/button-camera.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/button-camera.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/button-camera.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/button-camera.imageset/button-camera.pdf b/ImagePicker/Resources/Assets.xcassets/button-camera.imageset/button-camera.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/button-camera.imageset/button-camera.pdf rename to ImagePicker/Resources/Assets.xcassets/button-camera.imageset/button-camera.pdf diff --git a/ImagePicker/Assets.xcassets/button-photo-library.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/button-photo-library.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/button-photo-library.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/button-photo-library.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/button-photo-library.imageset/button-photo-library.pdf b/ImagePicker/Resources/Assets.xcassets/button-photo-library.imageset/button-photo-library.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/button-photo-library.imageset/button-photo-library.pdf rename to ImagePicker/Resources/Assets.xcassets/button-photo-library.imageset/button-photo-library.pdf diff --git a/ImagePicker/Assets.xcassets/gradient.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/gradient.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/gradient.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/gradient.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/gradient.imageset/gradient.png b/ImagePicker/Resources/Assets.xcassets/gradient.imageset/gradient.png old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/gradient.imageset/gradient.png rename to ImagePicker/Resources/Assets.xcassets/gradient.imageset/gradient.png diff --git a/ImagePicker/Assets.xcassets/gradient.imageset/gradient@2x.png b/ImagePicker/Resources/Assets.xcassets/gradient.imageset/gradient@2x.png old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/gradient.imageset/gradient@2x.png rename to ImagePicker/Resources/Assets.xcassets/gradient.imageset/gradient@2x.png diff --git a/ImagePicker/Assets.xcassets/gradient.imageset/gradient@3x.png b/ImagePicker/Resources/Assets.xcassets/gradient.imageset/gradient@3x.png old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/gradient.imageset/gradient@3x.png rename to ImagePicker/Resources/Assets.xcassets/gradient.imageset/gradient@3x.png diff --git a/ImagePicker/Assets.xcassets/icon-badge-livephoto.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/icon-badge-livephoto.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-badge-livephoto.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/icon-badge-livephoto.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/icon-badge-livephoto.imageset/icon-badge-livephoto.pdf b/ImagePicker/Resources/Assets.xcassets/icon-badge-livephoto.imageset/icon-badge-livephoto.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-badge-livephoto.imageset/icon-badge-livephoto.pdf rename to ImagePicker/Resources/Assets.xcassets/icon-badge-livephoto.imageset/icon-badge-livephoto.pdf diff --git a/ImagePicker/Assets.xcassets/icon-badge-video.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/icon-badge-video.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-badge-video.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/icon-badge-video.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/icon-badge-video.imageset/icon-badge-video.pdf b/ImagePicker/Resources/Assets.xcassets/icon-badge-video.imageset/icon-badge-video.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-badge-video.imageset/icon-badge-video.pdf rename to ImagePicker/Resources/Assets.xcassets/icon-badge-video.imageset/icon-badge-video.pdf diff --git a/ImagePicker/Assets.xcassets/icon-check-background.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/icon-check-background.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-check-background.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/icon-check-background.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/icon-check-background.imageset/icon-ckeck-background.pdf b/ImagePicker/Resources/Assets.xcassets/icon-check-background.imageset/icon-ckeck-background.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-check-background.imageset/icon-ckeck-background.pdf rename to ImagePicker/Resources/Assets.xcassets/icon-check-background.imageset/icon-ckeck-background.pdf diff --git a/ImagePicker/Assets.xcassets/icon-check.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/icon-check.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-check.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/icon-check.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/icon-check.imageset/icon-check.pdf b/ImagePicker/Resources/Assets.xcassets/icon-check.imageset/icon-check.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-check.imageset/icon-check.pdf rename to ImagePicker/Resources/Assets.xcassets/icon-check.imageset/icon-check.pdf diff --git a/ImagePicker/Assets.xcassets/icon-flip-camera.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/icon-flip-camera.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-flip-camera.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/icon-flip-camera.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/icon-flip-camera.imageset/flipCamera.pdf b/ImagePicker/Resources/Assets.xcassets/icon-flip-camera.imageset/flipCamera.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-flip-camera.imageset/flipCamera.pdf rename to ImagePicker/Resources/Assets.xcassets/icon-flip-camera.imageset/flipCamera.pdf diff --git a/ImagePicker/Assets.xcassets/icon-live-off.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/icon-live-off.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-live-off.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/icon-live-off.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/icon-live-off.imageset/icon-live-off.pdf b/ImagePicker/Resources/Assets.xcassets/icon-live-off.imageset/icon-live-off.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-live-off.imageset/icon-live-off.pdf rename to ImagePicker/Resources/Assets.xcassets/icon-live-off.imageset/icon-live-off.pdf diff --git a/ImagePicker/Assets.xcassets/icon-live-on.imageset/Contents.json b/ImagePicker/Resources/Assets.xcassets/icon-live-on.imageset/Contents.json old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-live-on.imageset/Contents.json rename to ImagePicker/Resources/Assets.xcassets/icon-live-on.imageset/Contents.json diff --git a/ImagePicker/Assets.xcassets/icon-live-on.imageset/icon-live-on.pdf b/ImagePicker/Resources/Assets.xcassets/icon-live-on.imageset/icon-live-on.pdf old mode 100644 new mode 100755 similarity index 100% rename from ImagePicker/Assets.xcassets/icon-live-on.imageset/icon-live-on.pdf rename to ImagePicker/Resources/Assets.xcassets/icon-live-on.imageset/icon-live-on.pdf diff --git a/ImagePicker/ActionCell.xib b/ImagePicker/Resources/XIB/ActionCell.xib old mode 100644 new mode 100755 similarity index 96% rename from ImagePicker/ActionCell.xib rename to ImagePicker/Resources/XIB/ActionCell.xib index 57cbecf..8898c84 --- a/ImagePicker/ActionCell.xib +++ b/ImagePicker/Resources/XIB/ActionCell.xib @@ -1,17 +1,17 @@ - + - + - + @@ -34,7 +34,7 @@