├── .gitignore ├── LICENSE ├── LLSimpleCamera.podspec ├── LLSimpleCamera ├── LLSimpleCamera+Helper.h ├── LLSimpleCamera+Helper.m ├── LLSimpleCamera.h ├── LLSimpleCamera.m ├── UIImage+FixOrientation.h └── UIImage+FixOrientation.m ├── LLSimpleCameraExample.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── LLSimpleCameraExample ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ └── LaunchScreen.xib ├── HomeViewController.h ├── HomeViewController.m ├── ImageViewController.h ├── ImageViewController.m ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── UIImage+Crop.h ├── UIImage+Crop.m ├── UIImage+Resize.h ├── UIImage+Resize.m ├── VideoViewController.h ├── VideoViewController.m ├── ViewUtils.h ├── ViewUtils.m ├── camera-flash.png ├── camera-flash@2x.png ├── camera-switch.png ├── camera-switch@2x.png ├── cancel.png ├── cancel@2x.png ├── cancel@3x.png └── main.m ├── LLSimpleCameraExampleTests ├── Info.plist └── LLSimpleCameraExampleTests.m ├── README.md └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | .DS_Store 21 | 22 | # CocoaPods 23 | # 24 | # We recommend against adding the Pods directory to your .gitignore. However 25 | # you should judge for yourself, the pros and cons are mentioned at: 26 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 27 | # 28 | # Pods/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License, Version 2.0 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ 2 | 3 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 4 | 5 | 1. Definitions. 6 | 7 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 8 | 9 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 10 | 11 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 12 | 13 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 14 | 15 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 16 | 17 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 18 | 19 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 20 | 21 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 22 | 23 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 24 | 25 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 26 | 27 | 2. Grant of Copyright License. 28 | 29 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 30 | 31 | 3. Grant of Patent License. 32 | 33 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. 36 | 37 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 38 | 39 | You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 40 | 41 | 5. Submission of Contributions. 42 | 43 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 44 | 45 | 6. Trademarks. 46 | 47 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. 50 | 51 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 52 | 53 | 8. Limitation of Liability. 54 | 55 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 56 | 57 | 9. Accepting Warranty or Additional Liability. 58 | 59 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 60 | 61 | END OF TERMS AND CONDITIONS 62 | 63 | APPENDIX: How to apply the Apache License to your work 64 | 65 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 66 | 67 | Copyright [yyyy] [name of copyright owner] 68 | 69 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 70 | 71 | http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /LLSimpleCamera.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "LLSimpleCamera" 3 | s.version = "5.0.0" 4 | s.summary = "LLSimpleCamera: A simple customizable camera - video recorder control." 5 | s.description = <<-DESC 6 | LLSimpleCamera is a library for creating a customized camera screens similar to snapchat's. You don't have to present the camera in a new view controller. You can capture images or record videos very easily. 7 | 8 | LLSimpleCamera: 9 | will let you easily capture photos or record videos 10 | handles the position and flash of the camera 11 | hides the nitty gritty details from the developer 12 | DESC 13 | 14 | s.homepage = "https://github.com/omergul123/LLSimpleCamera" 15 | s.license = { :type => 'APACHE', :file => 'LICENSE' } 16 | s.author = { "Ömer Faruk Gül" => "omergul123@gmail.com" } 17 | s.platform = :ios,'7.0' 18 | s.source = { :git => "https://github.com/omergul123/LLSimpleCamera.git", :tag => "v5.0.0" } 19 | s.source_files = 'LLSimpleCamera/*.{h,m}' 20 | s.requires_arc = true 21 | s.framework = 'AVFoundation' 22 | end 23 | -------------------------------------------------------------------------------- /LLSimpleCamera/LLSimpleCamera+Helper.h: -------------------------------------------------------------------------------- 1 | // 2 | // LLSimpleCamera+Helper.h 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 20/02/16. 6 | // Copyright © 2016 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import "LLSimpleCamera.h" 10 | 11 | @interface LLSimpleCamera (Helper) 12 | 13 | - (CGPoint)convertToPointOfInterestFromViewCoordinates:(CGPoint)viewCoordinates 14 | previewLayer:(AVCaptureVideoPreviewLayer *)previewLayer 15 | ports:(NSArray *)ports; 16 | 17 | - (UIImage *)cropImage:(UIImage *)image usingPreviewLayer:(AVCaptureVideoPreviewLayer *)previewLayer; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /LLSimpleCamera/LLSimpleCamera+Helper.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLSimpleCamera+Helper.m 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 20/02/16. 6 | // Copyright © 2016 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import "LLSimpleCamera+Helper.h" 10 | 11 | @implementation LLSimpleCamera (Helper) 12 | 13 | - (CGPoint)convertToPointOfInterestFromViewCoordinates:(CGPoint)viewCoordinates 14 | previewLayer:(AVCaptureVideoPreviewLayer *)previewLayer 15 | ports:(NSArray *)ports 16 | { 17 | CGPoint pointOfInterest = CGPointMake(.5f, .5f); 18 | CGSize frameSize = previewLayer.frame.size; 19 | 20 | if ( [previewLayer.videoGravity isEqualToString:AVLayerVideoGravityResize] ) { 21 | pointOfInterest = CGPointMake(viewCoordinates.y / frameSize.height, 1.f - (viewCoordinates.x / frameSize.width)); 22 | } else { 23 | CGRect cleanAperture; 24 | for (AVCaptureInputPort *port in ports) { 25 | if (port.mediaType == AVMediaTypeVideo) { 26 | cleanAperture = CMVideoFormatDescriptionGetCleanAperture([port formatDescription], YES); 27 | CGSize apertureSize = cleanAperture.size; 28 | CGPoint point = viewCoordinates; 29 | 30 | CGFloat apertureRatio = apertureSize.height / apertureSize.width; 31 | CGFloat viewRatio = frameSize.width / frameSize.height; 32 | CGFloat xc = .5f; 33 | CGFloat yc = .5f; 34 | 35 | if ( [previewLayer.videoGravity isEqualToString:AVLayerVideoGravityResizeAspect] ) { 36 | if (viewRatio > apertureRatio) { 37 | CGFloat y2 = frameSize.height; 38 | CGFloat x2 = frameSize.height * apertureRatio; 39 | CGFloat x1 = frameSize.width; 40 | CGFloat blackBar = (x1 - x2) / 2; 41 | if (point.x >= blackBar && point.x <= blackBar + x2) { 42 | xc = point.y / y2; 43 | yc = 1.f - ((point.x - blackBar) / x2); 44 | } 45 | } else { 46 | CGFloat y2 = frameSize.width / apertureRatio; 47 | CGFloat y1 = frameSize.height; 48 | CGFloat x2 = frameSize.width; 49 | CGFloat blackBar = (y1 - y2) / 2; 50 | if (point.y >= blackBar && point.y <= blackBar + y2) { 51 | xc = ((point.y - blackBar) / y2); 52 | yc = 1.f - (point.x / x2); 53 | } 54 | } 55 | } else if ([previewLayer.videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { 56 | if (viewRatio > apertureRatio) { 57 | CGFloat y2 = apertureSize.width * (frameSize.width / apertureSize.height); 58 | xc = (point.y + ((y2 - frameSize.height) / 2.f)) / y2; 59 | yc = (frameSize.width - point.x) / frameSize.width; 60 | } else { 61 | CGFloat x2 = apertureSize.height * (frameSize.height / apertureSize.width); 62 | yc = 1.f - ((point.x + ((x2 - frameSize.width) / 2)) / x2); 63 | xc = point.y / frameSize.height; 64 | } 65 | } 66 | 67 | pointOfInterest = CGPointMake(xc, yc); 68 | break; 69 | } 70 | } 71 | } 72 | 73 | return pointOfInterest; 74 | } 75 | 76 | - (UIImage *)cropImage:(UIImage *)image usingPreviewLayer:(AVCaptureVideoPreviewLayer *)previewLayer 77 | { 78 | CGRect previewBounds = previewLayer.bounds; 79 | CGRect outputRect = [previewLayer metadataOutputRectOfInterestForRect:previewBounds]; 80 | 81 | CGImageRef takenCGImage = image.CGImage; 82 | size_t width = CGImageGetWidth(takenCGImage); 83 | size_t height = CGImageGetHeight(takenCGImage); 84 | CGRect cropRect = CGRectMake(outputRect.origin.x * width, outputRect.origin.y * height, 85 | outputRect.size.width * width, outputRect.size.height * height); 86 | 87 | CGImageRef cropCGImage = CGImageCreateWithImageInRect(takenCGImage, cropRect); 88 | image = [UIImage imageWithCGImage:cropCGImage scale:1 orientation:image.imageOrientation]; 89 | CGImageRelease(cropCGImage); 90 | 91 | return image; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /LLSimpleCamera/LLSimpleCamera.h: -------------------------------------------------------------------------------- 1 | // 2 | // CameraViewController.h 3 | // LLSimpleCamera 4 | // 5 | // Created by Ömer Faruk Gül on 24/10/14. 6 | // Copyright (c) 2014 Ömer Farul Gül. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef enum : NSUInteger { 13 | LLCameraPositionRear, 14 | LLCameraPositionFront 15 | } LLCameraPosition; 16 | 17 | typedef enum : NSUInteger { 18 | // The default state has to be off 19 | LLCameraFlashOff, 20 | LLCameraFlashOn, 21 | LLCameraFlashAuto 22 | } LLCameraFlash; 23 | 24 | typedef enum : NSUInteger { 25 | // The default state has to be off 26 | LLCameraMirrorOff, 27 | LLCameraMirrorOn, 28 | LLCameraMirrorAuto 29 | } LLCameraMirror; 30 | 31 | extern NSString *const LLSimpleCameraErrorDomain; 32 | typedef enum : NSUInteger { 33 | LLSimpleCameraErrorCodeCameraPermission = 10, 34 | LLSimpleCameraErrorCodeMicrophonePermission = 11, 35 | LLSimpleCameraErrorCodeSession = 12, 36 | LLSimpleCameraErrorCodeVideoNotEnabled = 13 37 | } LLSimpleCameraErrorCode; 38 | 39 | @interface LLSimpleCamera : UIViewController 40 | 41 | /** 42 | * Triggered on device change. 43 | */ 44 | @property (nonatomic, copy) void (^onDeviceChange)(LLSimpleCamera *camera, AVCaptureDevice *device); 45 | 46 | /** 47 | * Triggered on any kind of error. 48 | */ 49 | @property (nonatomic, copy) void (^onError)(LLSimpleCamera *camera, NSError *error); 50 | 51 | /** 52 | * Triggered when camera starts recording 53 | */ 54 | @property (nonatomic, copy) void (^onStartRecording)(LLSimpleCamera* camera); 55 | 56 | /** 57 | * Camera quality, set a constants prefixed with AVCaptureSessionPreset. 58 | * Make sure to call before calling -(void)initialize method, otherwise it would be late. 59 | */ 60 | @property (copy, nonatomic) NSString *cameraQuality; 61 | 62 | /** 63 | * Camera flash mode. 64 | */ 65 | @property (nonatomic, readonly) LLCameraFlash flash; 66 | 67 | /** 68 | * Camera mirror mode. 69 | */ 70 | @property (nonatomic) LLCameraMirror mirror; 71 | 72 | /** 73 | * Position of the camera. 74 | */ 75 | @property (nonatomic) LLCameraPosition position; 76 | 77 | /** 78 | * White balance mode. Default is: AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance 79 | */ 80 | @property (nonatomic) AVCaptureWhiteBalanceMode whiteBalanceMode; 81 | 82 | /** 83 | * Boolean value to indicate if the video is enabled. 84 | */ 85 | @property (nonatomic, getter=isVideoEnabled) BOOL videoEnabled; 86 | 87 | /** 88 | * Boolean value to indicate if the camera is recording a video at the current moment. 89 | */ 90 | @property (nonatomic, getter=isRecording) BOOL recording; 91 | 92 | /** 93 | * Boolean value to indicate if zooming is enabled. 94 | */ 95 | @property (nonatomic, getter=isZoomingEnabled) BOOL zoomingEnabled; 96 | 97 | /** 98 | * Float value to set maximum scaling factor 99 | */ 100 | @property (nonatomic, assign) CGFloat maxScale; 101 | 102 | /** 103 | * Fixess the orientation after the image is captured is set to Yes. 104 | * see: http://stackoverflow.com/questions/5427656/ios-uiimagepickercontroller-result-image-orientation-after-upload 105 | */ 106 | @property (nonatomic) BOOL fixOrientationAfterCapture; 107 | 108 | /** 109 | * Set NO if you don't want ot enable user triggered focusing. Enabled by default. 110 | */ 111 | @property (nonatomic) BOOL tapToFocus; 112 | 113 | /** 114 | * Set YES if you your view controller does not allow autorotation, 115 | * however you want to take the device rotation into account no matter what. Disabled by default. 116 | */ 117 | @property (nonatomic) BOOL useDeviceOrientation; 118 | 119 | /** 120 | * Use this method to request camera permission before initalizing LLSimpleCamera. 121 | */ 122 | + (void)requestCameraPermission:(void (^)(BOOL granted))completionBlock; 123 | 124 | /** 125 | * Use this method to request microphone permission before initalizing LLSimpleCamera. 126 | */ 127 | + (void)requestMicrophonePermission:(void (^)(BOOL granted))completionBlock; 128 | 129 | /** 130 | * Returns an instance of LLSimpleCamera with the given quality. 131 | * Quality parameter could be any variable starting with AVCaptureSessionPreset. 132 | */ 133 | - (instancetype)initWithQuality:(NSString *)quality position:(LLCameraPosition)position videoEnabled:(BOOL)videoEnabled; 134 | 135 | /** 136 | * Returns an instance of LLSimpleCamera with quality "AVCaptureSessionPresetHigh" and position "CameraPositionBack". 137 | * @param videEnabled: Set to YES to enable video recording. 138 | */ 139 | - (instancetype)initWithVideoEnabled:(BOOL)videoEnabled; 140 | 141 | /** 142 | * Starts running the camera session. 143 | */ 144 | - (void)start; 145 | 146 | /** 147 | * Stops the running camera session. Needs to be called when the app doesn't show the view. 148 | */ 149 | - (void)stop; 150 | 151 | 152 | /** 153 | * Capture an image. 154 | * @param onCapture a block triggered after the capturing the photo. 155 | * @param exactSeenImage If set YES, then the image is cropped to the exact size as the preview. So you get exactly what you see. 156 | * @param animationBlock you can create your own animation by playing with preview layer. 157 | */ 158 | -(void)capture:(void (^)(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error))onCapture exactSeenImage:(BOOL)exactSeenImage animationBlock:(void (^)(AVCaptureVideoPreviewLayer *))animationBlock; 159 | 160 | /** 161 | * Capture an image. 162 | * @param onCapture a block triggered after the capturing the photo. 163 | * @param exactSeenImage If set YES, then the image is cropped to the exact size as the preview. So you get exactly what you see. 164 | */ 165 | -(void)capture:(void (^)(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error))onCapture exactSeenImage:(BOOL)exactSeenImage; 166 | 167 | /** 168 | * Capture an image. 169 | * @param onCapture a block triggered after the capturing the photo. 170 | */ 171 | -(void)capture:(void (^)(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error))onCapture; 172 | 173 | /* 174 | * Start recording a video with a completion block. Video is saved to the given url. 175 | */ 176 | - (void)startRecordingWithOutputUrl:(NSURL *)url didRecord:(void (^)(LLSimpleCamera *camera, NSURL *outputFileUrl, NSError *error))completionBlock; 177 | 178 | /** 179 | * Stop recording video. 180 | */ 181 | - (void)stopRecording; 182 | 183 | /** 184 | * Attaches the LLSimpleCamera to another view controller with a frame. It basically adds the LLSimpleCamera as a 185 | * child vc to the given vc. 186 | * @param vc A view controller. 187 | * @param frame The frame of the camera. 188 | */ 189 | - (void)attachToViewController:(UIViewController *)vc withFrame:(CGRect)frame; 190 | 191 | /** 192 | * Changes the posiition of the camera (either back or front) and returns the final position. 193 | */ 194 | - (LLCameraPosition)togglePosition; 195 | 196 | /** 197 | * Update the flash mode of the camera. Returns true if it is successful. Otherwise false. 198 | */ 199 | - (BOOL)updateFlashMode:(LLCameraFlash)cameraFlash; 200 | 201 | /** 202 | * Checks if flash is avilable for the currently active device. 203 | */ 204 | - (BOOL)isFlashAvailable; 205 | 206 | /** 207 | * Checks if torch (flash for video) is avilable for the currently active device. 208 | */ 209 | - (BOOL)isTorchAvailable; 210 | 211 | /** 212 | * Alter the layer and the animation displayed when the user taps on screen. 213 | * @param layer Layer to be displayed 214 | * @param animation to be applied after the layer is shown 215 | */ 216 | - (void)alterFocusBox:(CALayer *)layer animation:(CAAnimation *)animation; 217 | 218 | /** 219 | * Checks is the front camera is available. 220 | */ 221 | + (BOOL)isFrontCameraAvailable; 222 | 223 | /** 224 | * Checks is the rear camera is available. 225 | */ 226 | + (BOOL)isRearCameraAvailable; 227 | @end 228 | -------------------------------------------------------------------------------- /LLSimpleCamera/LLSimpleCamera.m: -------------------------------------------------------------------------------- 1 | // 2 | // CameraViewController.m 3 | // LLSimpleCamera 4 | // 5 | // Created by Ömer Faruk Gül on 24/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import "LLSimpleCamera.h" 10 | #import 11 | #import "UIImage+FixOrientation.h" 12 | #import "LLSimpleCamera+Helper.h" 13 | 14 | @interface LLSimpleCamera () 15 | @property (strong, nonatomic) UIView *preview; 16 | @property (strong, nonatomic) AVCaptureStillImageOutput *stillImageOutput; 17 | @property (strong, nonatomic) AVCaptureSession *session; 18 | @property (strong, nonatomic) AVCaptureDevice *videoCaptureDevice; 19 | @property (strong, nonatomic) AVCaptureDevice *audioCaptureDevice; 20 | @property (strong, nonatomic) AVCaptureDeviceInput *videoDeviceInput; 21 | @property (strong, nonatomic) AVCaptureDeviceInput *audioDeviceInput; 22 | @property (strong, nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer; 23 | @property (strong, nonatomic) UITapGestureRecognizer *tapGesture; 24 | @property (strong, nonatomic) CALayer *focusBoxLayer; 25 | @property (strong, nonatomic) CAAnimation *focusBoxAnimation; 26 | @property (strong, nonatomic) AVCaptureMovieFileOutput *movieFileOutput; 27 | @property (strong, nonatomic) UIPinchGestureRecognizer *pinchGesture; 28 | @property (nonatomic, assign) CGFloat beginGestureScale; 29 | @property (nonatomic, assign) CGFloat effectiveScale; 30 | @property (nonatomic, copy) void (^didRecordCompletionBlock)(LLSimpleCamera *camera, NSURL *outputFileUrl, NSError *error); 31 | @end 32 | 33 | NSString *const LLSimpleCameraErrorDomain = @"LLSimpleCameraErrorDomain"; 34 | 35 | @implementation LLSimpleCamera 36 | 37 | #pragma mark - Initialize 38 | 39 | - (instancetype)init 40 | { 41 | return [self initWithVideoEnabled:NO]; 42 | } 43 | 44 | - (instancetype)initWithVideoEnabled:(BOOL)videoEnabled 45 | { 46 | return [self initWithQuality:AVCaptureSessionPresetHigh position:LLCameraPositionRear videoEnabled:videoEnabled]; 47 | } 48 | 49 | - (instancetype)initWithQuality:(NSString *)quality position:(LLCameraPosition)position videoEnabled:(BOOL)videoEnabled 50 | { 51 | self = [super initWithNibName:nil bundle:nil]; 52 | if(self) { 53 | [self setupWithQuality:quality position:position videoEnabled:videoEnabled]; 54 | } 55 | 56 | return self; 57 | } 58 | 59 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 60 | { 61 | if (self = [super initWithCoder:aDecoder]) { 62 | [self setupWithQuality:AVCaptureSessionPresetHigh 63 | position:LLCameraPositionRear 64 | videoEnabled:YES]; 65 | } 66 | return self; 67 | } 68 | 69 | - (void)setupWithQuality:(NSString *)quality 70 | position:(LLCameraPosition)position 71 | videoEnabled:(BOOL)videoEnabled 72 | { 73 | _cameraQuality = quality; 74 | _position = position; 75 | _fixOrientationAfterCapture = NO; 76 | _tapToFocus = YES; 77 | _useDeviceOrientation = NO; 78 | _flash = LLCameraFlashOff; 79 | _mirror = LLCameraMirrorAuto; 80 | _videoEnabled = videoEnabled; 81 | _recording = NO; 82 | _zoomingEnabled = YES; 83 | _effectiveScale = 1.0f; 84 | } 85 | 86 | - (void)viewDidLoad 87 | { 88 | [super viewDidLoad]; 89 | 90 | self.view.backgroundColor = [UIColor clearColor]; 91 | self.view.autoresizingMask = UIViewAutoresizingNone; 92 | 93 | self.preview = [[UIView alloc] initWithFrame:CGRectZero]; 94 | self.preview.backgroundColor = [UIColor clearColor]; 95 | [self.view addSubview:self.preview]; 96 | 97 | // tap to focus 98 | self.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(previewTapped:)]; 99 | self.tapGesture.numberOfTapsRequired = 1; 100 | [self.tapGesture setDelaysTouchesEnded:NO]; 101 | [self.preview addGestureRecognizer:self.tapGesture]; 102 | 103 | //pinch to zoom 104 | if (_zoomingEnabled) { 105 | self.pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; 106 | self.pinchGesture.delegate = self; 107 | [self.preview addGestureRecognizer:self.pinchGesture]; 108 | } 109 | 110 | // add focus box to view 111 | [self addDefaultFocusBox]; 112 | } 113 | 114 | #pragma mark Pinch Delegate 115 | 116 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 117 | { 118 | if ( [gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] ) { 119 | _beginGestureScale = _effectiveScale; 120 | } 121 | return YES; 122 | } 123 | 124 | - (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer 125 | { 126 | BOOL allTouchesAreOnThePreviewLayer = YES; 127 | NSUInteger numTouches = [recognizer numberOfTouches], i; 128 | for ( i = 0; i < numTouches; ++i ) { 129 | CGPoint location = [recognizer locationOfTouch:i inView:self.preview]; 130 | CGPoint convertedLocation = [self.preview.layer convertPoint:location fromLayer:self.view.layer]; 131 | if ( ! [self.preview.layer containsPoint:convertedLocation] ) { 132 | allTouchesAreOnThePreviewLayer = NO; 133 | break; 134 | } 135 | } 136 | 137 | if (allTouchesAreOnThePreviewLayer) { 138 | _effectiveScale = _beginGestureScale * recognizer.scale; 139 | if (_effectiveScale < 1.0f) 140 | _effectiveScale = 1.0f; 141 | if (_effectiveScale > self.videoCaptureDevice.activeFormat.videoMaxZoomFactor) 142 | _effectiveScale = self.videoCaptureDevice.activeFormat.videoMaxZoomFactor; 143 | NSError *error = nil; 144 | if ([self.videoCaptureDevice lockForConfiguration:&error]) { 145 | [self.videoCaptureDevice rampToVideoZoomFactor:_effectiveScale withRate:100]; 146 | [self.videoCaptureDevice unlockForConfiguration]; 147 | } else { 148 | [self passError:error]; 149 | } 150 | } 151 | } 152 | 153 | #pragma mark - Camera 154 | 155 | - (void)attachToViewController:(UIViewController *)vc withFrame:(CGRect)frame 156 | { 157 | [vc addChildViewController:self]; 158 | self.view.frame = frame; 159 | [vc.view addSubview:self.view]; 160 | [self didMoveToParentViewController:vc]; 161 | } 162 | 163 | - (void)start 164 | { 165 | [LLSimpleCamera requestCameraPermission:^(BOOL granted) { 166 | if(granted) { 167 | // request microphone permission if video is enabled 168 | if(self.videoEnabled) { 169 | [LLSimpleCamera requestMicrophonePermission:^(BOOL granted) { 170 | if(granted) { 171 | [self initialize]; 172 | } 173 | else { 174 | NSError *error = [NSError errorWithDomain:LLSimpleCameraErrorDomain 175 | code:LLSimpleCameraErrorCodeMicrophonePermission 176 | userInfo:nil]; 177 | [self passError:error]; 178 | } 179 | }]; 180 | } 181 | else { 182 | [self initialize]; 183 | } 184 | } 185 | else { 186 | NSError *error = [NSError errorWithDomain:LLSimpleCameraErrorDomain 187 | code:LLSimpleCameraErrorCodeCameraPermission 188 | userInfo:nil]; 189 | [self passError:error]; 190 | } 191 | }]; 192 | } 193 | 194 | - (void)initialize 195 | { 196 | if(!_session) { 197 | _session = [[AVCaptureSession alloc] init]; 198 | _session.sessionPreset = self.cameraQuality; 199 | 200 | // preview layer 201 | CGRect bounds = self.preview.layer.bounds; 202 | _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session]; 203 | _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 204 | _captureVideoPreviewLayer.bounds = bounds; 205 | _captureVideoPreviewLayer.position = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); 206 | [self.preview.layer addSublayer:_captureVideoPreviewLayer]; 207 | 208 | AVCaptureDevicePosition devicePosition; 209 | switch (self.position) { 210 | case LLCameraPositionRear: 211 | if([self.class isRearCameraAvailable]) { 212 | devicePosition = AVCaptureDevicePositionBack; 213 | } else { 214 | devicePosition = AVCaptureDevicePositionFront; 215 | _position = LLCameraPositionFront; 216 | } 217 | break; 218 | case LLCameraPositionFront: 219 | if([self.class isFrontCameraAvailable]) { 220 | devicePosition = AVCaptureDevicePositionFront; 221 | } else { 222 | devicePosition = AVCaptureDevicePositionBack; 223 | _position = LLCameraPositionRear; 224 | } 225 | break; 226 | default: 227 | devicePosition = AVCaptureDevicePositionUnspecified; 228 | break; 229 | } 230 | 231 | if(devicePosition == AVCaptureDevicePositionUnspecified) { 232 | self.videoCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 233 | } else { 234 | self.videoCaptureDevice = [self cameraWithPosition:devicePosition]; 235 | } 236 | 237 | NSError *error = nil; 238 | _videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:_videoCaptureDevice error:&error]; 239 | 240 | if (!_videoDeviceInput) { 241 | [self passError:error]; 242 | return; 243 | } 244 | 245 | if([self.session canAddInput:_videoDeviceInput]) { 246 | [self.session addInput:_videoDeviceInput]; 247 | self.captureVideoPreviewLayer.connection.videoOrientation = [self orientationForConnection]; 248 | } 249 | 250 | // add audio if video is enabled 251 | if(self.videoEnabled) { 252 | _audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; 253 | _audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:_audioCaptureDevice error:&error]; 254 | if (!_audioDeviceInput) { 255 | [self passError:error]; 256 | } 257 | 258 | if([self.session canAddInput:_audioDeviceInput]) { 259 | [self.session addInput:_audioDeviceInput]; 260 | } 261 | 262 | _movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; 263 | [_movieFileOutput setMovieFragmentInterval:kCMTimeInvalid]; 264 | if([self.session canAddOutput:_movieFileOutput]) { 265 | [self.session addOutput:_movieFileOutput]; 266 | } 267 | } 268 | 269 | // continiously adjust white balance 270 | self.whiteBalanceMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance; 271 | 272 | // image output 273 | self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; 274 | NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys: AVVideoCodecJPEG, AVVideoCodecKey, nil]; 275 | [self.stillImageOutput setOutputSettings:outputSettings]; 276 | [self.session addOutput:self.stillImageOutput]; 277 | } 278 | 279 | //if we had disabled the connection on capture, re-enable it 280 | if (![self.captureVideoPreviewLayer.connection isEnabled]) { 281 | [self.captureVideoPreviewLayer.connection setEnabled:YES]; 282 | } 283 | 284 | [self.session startRunning]; 285 | } 286 | 287 | - (void)stop 288 | { 289 | [self.session stopRunning]; 290 | self.session = nil; 291 | } 292 | 293 | 294 | #pragma mark - Image Capture 295 | 296 | -(void)capture:(void (^)(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error))onCapture exactSeenImage:(BOOL)exactSeenImage animationBlock:(void (^)(AVCaptureVideoPreviewLayer *))animationBlock 297 | { 298 | if(!self.session) { 299 | NSError *error = [NSError errorWithDomain:LLSimpleCameraErrorDomain 300 | code:LLSimpleCameraErrorCodeSession 301 | userInfo:nil]; 302 | onCapture(self, nil, nil, error); 303 | return; 304 | } 305 | 306 | // get connection and set orientation 307 | AVCaptureConnection *videoConnection = [self captureConnection]; 308 | videoConnection.videoOrientation = [self orientationForConnection]; 309 | 310 | BOOL flashActive = self.videoCaptureDevice.flashActive; 311 | if (!flashActive && animationBlock) { 312 | animationBlock(self.captureVideoPreviewLayer); 313 | } 314 | 315 | [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) { 316 | 317 | UIImage *image = nil; 318 | NSDictionary *metadata = nil; 319 | 320 | // check if we got the image buffer 321 | if (imageSampleBuffer != NULL) { 322 | CFDictionaryRef exifAttachments = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL); 323 | if(exifAttachments) { 324 | metadata = (__bridge NSDictionary*)exifAttachments; 325 | } 326 | 327 | NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer]; 328 | image = [[UIImage alloc] initWithData:imageData]; 329 | 330 | if(exactSeenImage) { 331 | image = [self cropImage:image usingPreviewLayer:self.captureVideoPreviewLayer]; 332 | } 333 | 334 | if(self.fixOrientationAfterCapture) { 335 | image = [image fixOrientation]; 336 | } 337 | } 338 | 339 | // trigger the block 340 | if(onCapture) { 341 | dispatch_async(dispatch_get_main_queue(), ^{ 342 | onCapture(self, image, metadata, error); 343 | }); 344 | } 345 | }]; 346 | } 347 | 348 | -(void)capture:(void (^)(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error))onCapture exactSeenImage:(BOOL)exactSeenImage { 349 | 350 | [self capture:onCapture exactSeenImage:exactSeenImage animationBlock:^(AVCaptureVideoPreviewLayer *layer) { 351 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 352 | animation.duration = 0.1; 353 | animation.autoreverses = YES; 354 | animation.repeatCount = 0.0; 355 | animation.fromValue = [NSNumber numberWithFloat:1.0]; 356 | animation.toValue = [NSNumber numberWithFloat:0.1]; 357 | animation.fillMode = kCAFillModeForwards; 358 | animation.removedOnCompletion = NO; 359 | [layer addAnimation:animation forKey:@"animateOpacity"]; 360 | }]; 361 | } 362 | 363 | -(void)capture:(void (^)(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error))onCapture 364 | { 365 | [self capture:onCapture exactSeenImage:NO]; 366 | } 367 | 368 | #pragma mark - Video Capture 369 | 370 | - (void)startRecordingWithOutputUrl:(NSURL *)url didRecord:(void (^)(LLSimpleCamera *camera, NSURL *outputFileUrl, NSError *error))completionBlock 371 | { 372 | // check if video is enabled 373 | if(!self.videoEnabled) { 374 | NSError *error = [NSError errorWithDomain:LLSimpleCameraErrorDomain 375 | code:LLSimpleCameraErrorCodeVideoNotEnabled 376 | userInfo:nil]; 377 | [self passError:error]; 378 | return; 379 | } 380 | 381 | if(self.flash == LLCameraFlashOn) { 382 | [self enableTorch:YES]; 383 | } 384 | 385 | // set video orientation 386 | for(AVCaptureConnection *connection in [self.movieFileOutput connections]) { 387 | for (AVCaptureInputPort *port in [connection inputPorts]) { 388 | // get only the video media types 389 | if ([[port mediaType] isEqual:AVMediaTypeVideo]) { 390 | if ([connection isVideoOrientationSupported]) { 391 | [connection setVideoOrientation:[self orientationForConnection]]; 392 | } 393 | } 394 | } 395 | } 396 | 397 | self.didRecordCompletionBlock = completionBlock; 398 | 399 | [self.movieFileOutput startRecordingToOutputFileURL:url recordingDelegate:self]; 400 | } 401 | 402 | - (void)stopRecording 403 | { 404 | if(!self.videoEnabled) { 405 | return; 406 | } 407 | 408 | [self.movieFileOutput stopRecording]; 409 | } 410 | 411 | - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections 412 | { 413 | self.recording = YES; 414 | if(self.onStartRecording) self.onStartRecording(self); 415 | } 416 | 417 | - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error 418 | { 419 | self.recording = NO; 420 | [self enableTorch:NO]; 421 | 422 | if(self.didRecordCompletionBlock) { 423 | self.didRecordCompletionBlock(self, outputFileURL, error); 424 | } 425 | } 426 | 427 | - (void)enableTorch:(BOOL)enabled 428 | { 429 | // check if the device has a torch, otherwise don't do anything 430 | if([self isTorchAvailable]) { 431 | AVCaptureTorchMode torchMode = enabled ? AVCaptureTorchModeOn : AVCaptureTorchModeOff; 432 | NSError *error; 433 | if ([self.videoCaptureDevice lockForConfiguration:&error]) { 434 | [self.videoCaptureDevice setTorchMode:torchMode]; 435 | [self.videoCaptureDevice unlockForConfiguration]; 436 | } else { 437 | [self passError:error]; 438 | } 439 | } 440 | } 441 | 442 | #pragma mark - Helpers 443 | 444 | - (void)passError:(NSError *)error 445 | { 446 | if(self.onError) { 447 | __weak typeof(self) weakSelf = self; 448 | self.onError(weakSelf, error); 449 | } 450 | } 451 | 452 | - (AVCaptureConnection *)captureConnection 453 | { 454 | AVCaptureConnection *videoConnection = nil; 455 | for (AVCaptureConnection *connection in self.stillImageOutput.connections) { 456 | for (AVCaptureInputPort *port in [connection inputPorts]) { 457 | if ([[port mediaType] isEqual:AVMediaTypeVideo]) { 458 | videoConnection = connection; 459 | break; 460 | } 461 | } 462 | if (videoConnection) { 463 | break; 464 | } 465 | } 466 | 467 | return videoConnection; 468 | } 469 | 470 | - (void)setVideoCaptureDevice:(AVCaptureDevice *)videoCaptureDevice 471 | { 472 | _videoCaptureDevice = videoCaptureDevice; 473 | 474 | if(videoCaptureDevice.flashMode == AVCaptureFlashModeAuto) { 475 | _flash = LLCameraFlashAuto; 476 | } else if(videoCaptureDevice.flashMode == AVCaptureFlashModeOn) { 477 | _flash = LLCameraFlashOn; 478 | } else if(videoCaptureDevice.flashMode == AVCaptureFlashModeOff) { 479 | _flash = LLCameraFlashOff; 480 | } else { 481 | _flash = LLCameraFlashOff; 482 | } 483 | 484 | _effectiveScale = 1.0f; 485 | 486 | // trigger block 487 | if(self.onDeviceChange) { 488 | __weak typeof(self) weakSelf = self; 489 | self.onDeviceChange(weakSelf, videoCaptureDevice); 490 | } 491 | } 492 | 493 | - (BOOL)isFlashAvailable 494 | { 495 | return self.videoCaptureDevice.hasFlash && self.videoCaptureDevice.isFlashAvailable; 496 | } 497 | 498 | - (BOOL)isTorchAvailable 499 | { 500 | return self.videoCaptureDevice.hasTorch && self.videoCaptureDevice.isTorchAvailable; 501 | } 502 | 503 | - (BOOL)updateFlashMode:(LLCameraFlash)cameraFlash 504 | { 505 | if(!self.session) 506 | return NO; 507 | 508 | AVCaptureFlashMode flashMode; 509 | 510 | if(cameraFlash == LLCameraFlashOn) { 511 | flashMode = AVCaptureFlashModeOn; 512 | } else if(cameraFlash == LLCameraFlashAuto) { 513 | flashMode = AVCaptureFlashModeAuto; 514 | } else { 515 | flashMode = AVCaptureFlashModeOff; 516 | } 517 | 518 | if([self.videoCaptureDevice isFlashModeSupported:flashMode]) { 519 | NSError *error; 520 | if([self.videoCaptureDevice lockForConfiguration:&error]) { 521 | self.videoCaptureDevice.flashMode = flashMode; 522 | [self.videoCaptureDevice unlockForConfiguration]; 523 | 524 | _flash = cameraFlash; 525 | return YES; 526 | } else { 527 | [self passError:error]; 528 | return NO; 529 | } 530 | } 531 | else { 532 | return NO; 533 | } 534 | } 535 | 536 | - (void)setWhiteBalanceMode:(AVCaptureWhiteBalanceMode)whiteBalanceMode 537 | { 538 | if ([self.videoCaptureDevice isWhiteBalanceModeSupported:whiteBalanceMode]) { 539 | NSError *error; 540 | if ([self.videoCaptureDevice lockForConfiguration:&error]) { 541 | [self.videoCaptureDevice setWhiteBalanceMode:whiteBalanceMode]; 542 | [self.videoCaptureDevice unlockForConfiguration]; 543 | } else { 544 | [self passError:error]; 545 | } 546 | } 547 | } 548 | 549 | - (void)setMirror:(LLCameraMirror)mirror 550 | { 551 | _mirror = mirror; 552 | 553 | if(!self.session) { 554 | return; 555 | } 556 | 557 | AVCaptureConnection *videoConnection = [_movieFileOutput connectionWithMediaType:AVMediaTypeVideo]; 558 | AVCaptureConnection *pictureConnection = [_stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; 559 | 560 | switch (mirror) { 561 | case LLCameraMirrorOff: { 562 | if ([videoConnection isVideoMirroringSupported]) { 563 | [videoConnection setVideoMirrored:NO]; 564 | } 565 | 566 | if ([pictureConnection isVideoMirroringSupported]) { 567 | [pictureConnection setVideoMirrored:NO]; 568 | } 569 | break; 570 | } 571 | 572 | case LLCameraMirrorOn: { 573 | if ([videoConnection isVideoMirroringSupported]) { 574 | [videoConnection setVideoMirrored:YES]; 575 | } 576 | 577 | if ([pictureConnection isVideoMirroringSupported]) { 578 | [pictureConnection setVideoMirrored:YES]; 579 | } 580 | break; 581 | } 582 | 583 | case LLCameraMirrorAuto: { 584 | BOOL shouldMirror = (_position == LLCameraPositionFront); 585 | if ([videoConnection isVideoMirroringSupported]) { 586 | [videoConnection setVideoMirrored:shouldMirror]; 587 | } 588 | 589 | if ([pictureConnection isVideoMirroringSupported]) { 590 | [pictureConnection setVideoMirrored:shouldMirror]; 591 | } 592 | break; 593 | } 594 | } 595 | 596 | return; 597 | } 598 | 599 | - (LLCameraPosition)togglePosition 600 | { 601 | if(!self.session) { 602 | return self.position; 603 | } 604 | 605 | if(self.position == LLCameraPositionRear) { 606 | self.cameraPosition = LLCameraPositionFront; 607 | } else { 608 | self.cameraPosition = LLCameraPositionRear; 609 | } 610 | 611 | return self.position; 612 | } 613 | 614 | - (void)setCameraPosition:(LLCameraPosition)cameraPosition 615 | { 616 | if(_position == cameraPosition || !self.session) { 617 | return; 618 | } 619 | 620 | if(cameraPosition == LLCameraPositionRear && ![self.class isRearCameraAvailable]) { 621 | return; 622 | } 623 | 624 | if(cameraPosition == LLCameraPositionFront && ![self.class isFrontCameraAvailable]) { 625 | return; 626 | } 627 | 628 | [self.session beginConfiguration]; 629 | 630 | // remove existing input 631 | [self.session removeInput:self.videoDeviceInput]; 632 | 633 | // get new input 634 | AVCaptureDevice *device = nil; 635 | if(self.videoDeviceInput.device.position == AVCaptureDevicePositionBack) { 636 | device = [self cameraWithPosition:AVCaptureDevicePositionFront]; 637 | } else { 638 | device = [self cameraWithPosition:AVCaptureDevicePositionBack]; 639 | } 640 | 641 | if(!device) { 642 | return; 643 | } 644 | 645 | // add input to session 646 | NSError *error = nil; 647 | AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error]; 648 | if(error) { 649 | [self passError:error]; 650 | [self.session commitConfiguration]; 651 | return; 652 | } 653 | 654 | _position = cameraPosition; 655 | 656 | [self.session addInput:videoInput]; 657 | [self.session commitConfiguration]; 658 | 659 | self.videoCaptureDevice = device; 660 | self.videoDeviceInput = videoInput; 661 | 662 | [self setMirror:_mirror]; 663 | } 664 | 665 | 666 | // Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found 667 | - (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position 668 | { 669 | NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; 670 | for (AVCaptureDevice *device in devices) { 671 | if (device.position == position) return device; 672 | } 673 | return nil; 674 | } 675 | 676 | #pragma mark - Focus 677 | 678 | - (void)previewTapped:(UIGestureRecognizer *)gestureRecognizer 679 | { 680 | if(!self.tapToFocus) { 681 | return; 682 | } 683 | 684 | CGPoint touchedPoint = [gestureRecognizer locationInView:self.preview]; 685 | CGPoint pointOfInterest = [self convertToPointOfInterestFromViewCoordinates:touchedPoint 686 | previewLayer:self.captureVideoPreviewLayer 687 | ports:self.videoDeviceInput.ports]; 688 | [self focusAtPoint:pointOfInterest]; 689 | [self showFocusBox:touchedPoint]; 690 | } 691 | 692 | - (void)addDefaultFocusBox 693 | { 694 | CALayer *focusBox = [[CALayer alloc] init]; 695 | focusBox.cornerRadius = 5.0f; 696 | focusBox.bounds = CGRectMake(0.0f, 0.0f, 70, 60); 697 | focusBox.borderWidth = 3.0f; 698 | focusBox.borderColor = [[UIColor yellowColor] CGColor]; 699 | focusBox.opacity = 0.0f; 700 | [self.view.layer addSublayer:focusBox]; 701 | 702 | CABasicAnimation *focusBoxAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 703 | focusBoxAnimation.duration = 0.75; 704 | focusBoxAnimation.autoreverses = NO; 705 | focusBoxAnimation.repeatCount = 0.0; 706 | focusBoxAnimation.fromValue = [NSNumber numberWithFloat:1.0]; 707 | focusBoxAnimation.toValue = [NSNumber numberWithFloat:0.0]; 708 | 709 | [self alterFocusBox:focusBox animation:focusBoxAnimation]; 710 | } 711 | 712 | - (void)alterFocusBox:(CALayer *)layer animation:(CAAnimation *)animation 713 | { 714 | self.focusBoxLayer = layer; 715 | self.focusBoxAnimation = animation; 716 | } 717 | 718 | - (void)focusAtPoint:(CGPoint)point 719 | { 720 | AVCaptureDevice *device = self.videoCaptureDevice; 721 | if (device.isFocusPointOfInterestSupported && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) { 722 | NSError *error; 723 | if ([device lockForConfiguration:&error]) { 724 | device.focusPointOfInterest = point; 725 | device.focusMode = AVCaptureFocusModeAutoFocus; 726 | [device unlockForConfiguration]; 727 | } else { 728 | [self passError:error]; 729 | } 730 | } 731 | } 732 | 733 | - (void)showFocusBox:(CGPoint)point 734 | { 735 | if(self.focusBoxLayer) { 736 | // clear animations 737 | [self.focusBoxLayer removeAllAnimations]; 738 | 739 | // move layer to the touch point 740 | [CATransaction begin]; 741 | [CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions]; 742 | self.focusBoxLayer.position = point; 743 | [CATransaction commit]; 744 | } 745 | 746 | if(self.focusBoxAnimation) { 747 | // run the animation 748 | [self.focusBoxLayer addAnimation:self.focusBoxAnimation forKey:@"animateOpacity"]; 749 | } 750 | } 751 | 752 | #pragma mark - UIViewController 753 | 754 | - (void)viewWillAppear:(BOOL)animated 755 | { 756 | [super viewWillAppear:animated]; 757 | } 758 | 759 | - (void)viewWillDisappear:(BOOL)animated 760 | { 761 | [super viewWillDisappear:animated]; 762 | } 763 | 764 | - (void)viewWillLayoutSubviews 765 | { 766 | [super viewWillLayoutSubviews]; 767 | 768 | self.preview.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height); 769 | 770 | CGRect bounds = self.preview.bounds; 771 | self.captureVideoPreviewLayer.bounds = bounds; 772 | self.captureVideoPreviewLayer.position = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); 773 | 774 | self.captureVideoPreviewLayer.connection.videoOrientation = [self orientationForConnection]; 775 | } 776 | 777 | - (AVCaptureVideoOrientation)orientationForConnection 778 | { 779 | AVCaptureVideoOrientation videoOrientation = AVCaptureVideoOrientationPortrait; 780 | 781 | if(self.useDeviceOrientation) { 782 | switch ([UIDevice currentDevice].orientation) { 783 | case UIDeviceOrientationLandscapeLeft: 784 | // yes to the right, this is not bug! 785 | videoOrientation = AVCaptureVideoOrientationLandscapeRight; 786 | break; 787 | case UIDeviceOrientationLandscapeRight: 788 | videoOrientation = AVCaptureVideoOrientationLandscapeLeft; 789 | break; 790 | case UIDeviceOrientationPortraitUpsideDown: 791 | videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown; 792 | break; 793 | default: 794 | videoOrientation = AVCaptureVideoOrientationPortrait; 795 | break; 796 | } 797 | } 798 | else { 799 | switch ([[UIApplication sharedApplication] statusBarOrientation]) { 800 | case UIInterfaceOrientationLandscapeLeft: 801 | videoOrientation = AVCaptureVideoOrientationLandscapeLeft; 802 | break; 803 | case UIInterfaceOrientationLandscapeRight: 804 | videoOrientation = AVCaptureVideoOrientationLandscapeRight; 805 | break; 806 | case UIInterfaceOrientationPortraitUpsideDown: 807 | videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown; 808 | break; 809 | default: 810 | videoOrientation = AVCaptureVideoOrientationPortrait; 811 | break; 812 | } 813 | } 814 | 815 | return videoOrientation; 816 | } 817 | 818 | - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 819 | { 820 | [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; 821 | 822 | // layout subviews is not called when rotating from landscape right/left to left/right 823 | if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation) && UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) { 824 | [self.view setNeedsLayout]; 825 | } 826 | } 827 | 828 | - (void)didReceiveMemoryWarning 829 | { 830 | [super didReceiveMemoryWarning]; 831 | } 832 | 833 | - (void)dealloc { 834 | [self stop]; 835 | } 836 | 837 | #pragma mark - Class Methods 838 | 839 | + (void)requestCameraPermission:(void (^)(BOOL granted))completionBlock 840 | { 841 | if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType: completionHandler:)]) { 842 | [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { 843 | // return to main thread 844 | dispatch_async(dispatch_get_main_queue(), ^{ 845 | if(completionBlock) { 846 | completionBlock(granted); 847 | } 848 | }); 849 | }]; 850 | } else { 851 | completionBlock(YES); 852 | } 853 | } 854 | 855 | + (void)requestMicrophonePermission:(void (^)(BOOL granted))completionBlock 856 | { 857 | if([[AVAudioSession sharedInstance] respondsToSelector:@selector(requestRecordPermission:)]) { 858 | [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { 859 | // return to main thread 860 | dispatch_async(dispatch_get_main_queue(), ^{ 861 | if(completionBlock) { 862 | completionBlock(granted); 863 | } 864 | }); 865 | }]; 866 | } 867 | } 868 | 869 | + (BOOL)isFrontCameraAvailable 870 | { 871 | return [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]; 872 | } 873 | 874 | + (BOOL)isRearCameraAvailable 875 | { 876 | return [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear]; 877 | } 878 | 879 | @end 880 | -------------------------------------------------------------------------------- /LLSimpleCamera/UIImage+FixOrientation.h: -------------------------------------------------------------------------------- 1 | // http://stackoverflow.com/questions/5427656/ios-uiimagepickercontroller-result-image-orientation-after-upload 2 | 3 | 4 | 5 | #import 6 | 7 | @interface UIImage(fixOrientation) 8 | - (UIImage *)fixOrientation; 9 | @end 10 | -------------------------------------------------------------------------------- /LLSimpleCamera/UIImage+FixOrientation.m: -------------------------------------------------------------------------------- 1 | // http://stackoverflow.com/questions/5427656/ios-uiimagepickercontroller-result-image-orientation-after-upload 2 | 3 | #import "UIImage+FixOrientation.h" 4 | 5 | @implementation UIImage (fixOrientation) 6 | 7 | - (UIImage *)fixOrientation { 8 | 9 | // No-op if the orientation is already correct 10 | if (self.imageOrientation == UIImageOrientationUp) return self; 11 | 12 | // We need to calculate the proper transformation to make the image upright. 13 | // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored. 14 | CGAffineTransform transform = CGAffineTransformIdentity; 15 | 16 | switch (self.imageOrientation) { 17 | case UIImageOrientationDown: 18 | case UIImageOrientationDownMirrored: 19 | transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height); 20 | transform = CGAffineTransformRotate(transform, M_PI); 21 | break; 22 | 23 | case UIImageOrientationLeft: 24 | case UIImageOrientationLeftMirrored: 25 | transform = CGAffineTransformTranslate(transform, self.size.width, 0); 26 | transform = CGAffineTransformRotate(transform, M_PI_2); 27 | break; 28 | 29 | case UIImageOrientationRight: 30 | case UIImageOrientationRightMirrored: 31 | transform = CGAffineTransformTranslate(transform, 0, self.size.height); 32 | transform = CGAffineTransformRotate(transform, -M_PI_2); 33 | break; 34 | case UIImageOrientationUp: 35 | case UIImageOrientationUpMirrored: 36 | break; 37 | } 38 | 39 | switch (self.imageOrientation) { 40 | case UIImageOrientationUpMirrored: 41 | case UIImageOrientationDownMirrored: 42 | transform = CGAffineTransformTranslate(transform, self.size.width, 0); 43 | transform = CGAffineTransformScale(transform, -1, 1); 44 | break; 45 | 46 | case UIImageOrientationLeftMirrored: 47 | case UIImageOrientationRightMirrored: 48 | transform = CGAffineTransformTranslate(transform, self.size.height, 0); 49 | transform = CGAffineTransformScale(transform, -1, 1); 50 | break; 51 | case UIImageOrientationUp: 52 | case UIImageOrientationDown: 53 | case UIImageOrientationLeft: 54 | case UIImageOrientationRight: 55 | break; 56 | } 57 | 58 | // Now we draw the underlying CGImage into a new context, applying the transform 59 | // calculated above. 60 | CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height, 61 | CGImageGetBitsPerComponent(self.CGImage), 0, 62 | CGImageGetColorSpace(self.CGImage), 63 | CGImageGetBitmapInfo(self.CGImage)); 64 | CGContextConcatCTM(ctx, transform); 65 | switch (self.imageOrientation) { 66 | case UIImageOrientationLeft: 67 | case UIImageOrientationLeftMirrored: 68 | case UIImageOrientationRight: 69 | case UIImageOrientationRightMirrored: 70 | // Grr... 71 | CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage); 72 | break; 73 | 74 | default: 75 | CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage); 76 | break; 77 | } 78 | 79 | // And now we just create a new UIImage from the drawing context 80 | CGImageRef cgimg = CGBitmapContextCreateImage(ctx); 81 | UIImage *img = [UIImage imageWithCGImage:cgimg]; 82 | CGContextRelease(ctx); 83 | CGImageRelease(cgimg); 84 | return img; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /LLSimpleCameraExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 170030D11B122F3700585CEB /* VideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 170030D01B122F3700585CEB /* VideoViewController.m */; }; 11 | 170030D51B1231B400585CEB /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = 170030D21B1231B400585CEB /* cancel.png */; }; 12 | 170030D61B1231B400585CEB /* cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 170030D31B1231B400585CEB /* cancel@2x.png */; }; 13 | 170030D71B1231B400585CEB /* cancel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 170030D41B1231B400585CEB /* cancel@3x.png */; }; 14 | 170030DA1B1232D100585CEB /* camera-flash.png in Resources */ = {isa = PBXBuildFile; fileRef = 170030D81B1232D100585CEB /* camera-flash.png */; }; 15 | 170030DB1B1232D100585CEB /* camera-flash@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 170030D91B1232D100585CEB /* camera-flash@2x.png */; }; 16 | 1713621B1A0144100034A6FE /* UIImage+FixOrientation.m in Sources */ = {isa = PBXBuildFile; fileRef = 1713621A1A0144100034A6FE /* UIImage+FixOrientation.m */; }; 17 | 1733A8EE1C791BA5000B3648 /* LLSimpleCamera+Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = 1733A8ED1C791BA5000B3648 /* LLSimpleCamera+Helper.m */; }; 18 | 17BD92341A1776E20016070B /* ImageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17BD92331A1776E20016070B /* ImageViewController.m */; }; 19 | 17BD92641A17F0040016070B /* camera-switch.png in Resources */ = {isa = PBXBuildFile; fileRef = 17BD92621A17F0040016070B /* camera-switch.png */; }; 20 | 17BD92651A17F0040016070B /* camera-switch@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17BD92631A17F0040016070B /* camera-switch@2x.png */; }; 21 | 17E4C4851A0122A700E61ACD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E4C4841A0122A700E61ACD /* main.m */; }; 22 | 17E4C4881A0122A700E61ACD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E4C4871A0122A700E61ACD /* AppDelegate.m */; }; 23 | 17E4C4901A0122A700E61ACD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17E4C48F1A0122A700E61ACD /* Images.xcassets */; }; 24 | 17E4C4931A0122A700E61ACD /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17E4C4911A0122A700E61ACD /* LaunchScreen.xib */; }; 25 | 17E4C49F1A0122A700E61ACD /* LLSimpleCameraExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E4C49E1A0122A700E61ACD /* LLSimpleCameraExampleTests.m */; }; 26 | 17E4C4AB1A01235200E61ACD /* LLSimpleCamera.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E4C4AA1A01235200E61ACD /* LLSimpleCamera.m */; }; 27 | 17E4C4B31A0123C100E61ACD /* UIImage+Crop.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E4C4AE1A0123C100E61ACD /* UIImage+Crop.m */; }; 28 | 17E4C4B51A0123C100E61ACD /* UIImage+Resize.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E4C4B21A0123C100E61ACD /* UIImage+Resize.m */; }; 29 | 17E4C4B91A01285800E61ACD /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E4C4B81A01285800E61ACD /* HomeViewController.m */; }; 30 | 17E4C4BC1A0128EC00E61ACD /* ViewUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E4C4BB1A0128EC00E61ACD /* ViewUtils.m */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | 17E4C4991A0122A700E61ACD /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 17E4C4771A0122A700E61ACD /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 17E4C47E1A0122A700E61ACD; 39 | remoteInfo = LLSimpleCameraExample; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 170030CF1B122F3700585CEB /* VideoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoViewController.h; sourceTree = ""; }; 45 | 170030D01B122F3700585CEB /* VideoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoViewController.m; sourceTree = ""; }; 46 | 170030D21B1231B400585CEB /* cancel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cancel.png; sourceTree = ""; }; 47 | 170030D31B1231B400585CEB /* cancel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cancel@2x.png"; sourceTree = ""; }; 48 | 170030D41B1231B400585CEB /* cancel@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cancel@3x.png"; sourceTree = ""; }; 49 | 170030D81B1232D100585CEB /* camera-flash.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camera-flash.png"; sourceTree = ""; }; 50 | 170030D91B1232D100585CEB /* camera-flash@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camera-flash@2x.png"; sourceTree = ""; }; 51 | 171362191A0144100034A6FE /* UIImage+FixOrientation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+FixOrientation.h"; sourceTree = ""; }; 52 | 1713621A1A0144100034A6FE /* UIImage+FixOrientation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+FixOrientation.m"; sourceTree = ""; }; 53 | 1733A8EC1C791BA5000B3648 /* LLSimpleCamera+Helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LLSimpleCamera+Helper.h"; sourceTree = ""; }; 54 | 1733A8ED1C791BA5000B3648 /* LLSimpleCamera+Helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "LLSimpleCamera+Helper.m"; sourceTree = ""; }; 55 | 17BD92321A1776E20016070B /* ImageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageViewController.h; sourceTree = ""; }; 56 | 17BD92331A1776E20016070B /* ImageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageViewController.m; sourceTree = ""; }; 57 | 17BD92621A17F0040016070B /* camera-switch.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camera-switch.png"; sourceTree = ""; }; 58 | 17BD92631A17F0040016070B /* camera-switch@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camera-switch@2x.png"; sourceTree = ""; }; 59 | 17E4C47F1A0122A700E61ACD /* LLSimpleCameraExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LLSimpleCameraExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 17E4C4831A0122A700E61ACD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 17E4C4841A0122A700E61ACD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 62 | 17E4C4861A0122A700E61ACD /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 63 | 17E4C4871A0122A700E61ACD /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 64 | 17E4C48F1A0122A700E61ACD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 65 | 17E4C4921A0122A700E61ACD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 66 | 17E4C4981A0122A700E61ACD /* LLSimpleCameraExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LLSimpleCameraExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 17E4C49D1A0122A700E61ACD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | 17E4C49E1A0122A700E61ACD /* LLSimpleCameraExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LLSimpleCameraExampleTests.m; sourceTree = ""; }; 69 | 17E4C4A91A01235200E61ACD /* LLSimpleCamera.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLSimpleCamera.h; sourceTree = ""; }; 70 | 17E4C4AA1A01235200E61ACD /* LLSimpleCamera.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LLSimpleCamera.m; sourceTree = ""; }; 71 | 17E4C4AD1A0123C100E61ACD /* UIImage+Crop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Crop.h"; sourceTree = ""; }; 72 | 17E4C4AE1A0123C100E61ACD /* UIImage+Crop.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Crop.m"; sourceTree = ""; }; 73 | 17E4C4B11A0123C100E61ACD /* UIImage+Resize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Resize.h"; sourceTree = ""; }; 74 | 17E4C4B21A0123C100E61ACD /* UIImage+Resize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Resize.m"; sourceTree = ""; }; 75 | 17E4C4B71A01285800E61ACD /* HomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewController.h; sourceTree = ""; }; 76 | 17E4C4B81A01285800E61ACD /* HomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewController.m; sourceTree = ""; }; 77 | 17E4C4BA1A0128EC00E61ACD /* ViewUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewUtils.h; sourceTree = ""; }; 78 | 17E4C4BB1A0128EC00E61ACD /* ViewUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewUtils.m; sourceTree = ""; }; 79 | /* End PBXFileReference section */ 80 | 81 | /* Begin PBXFrameworksBuildPhase section */ 82 | 17E4C47C1A0122A700E61ACD /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | 17E4C4951A0122A700E61ACD /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | 17BD92411A17DB150016070B /* Images */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 17BD92621A17F0040016070B /* camera-switch.png */, 103 | 17BD92631A17F0040016070B /* camera-switch@2x.png */, 104 | 170030D81B1232D100585CEB /* camera-flash.png */, 105 | 170030D91B1232D100585CEB /* camera-flash@2x.png */, 106 | 170030D21B1231B400585CEB /* cancel.png */, 107 | 170030D31B1231B400585CEB /* cancel@2x.png */, 108 | 170030D41B1231B400585CEB /* cancel@3x.png */, 109 | ); 110 | name = Images; 111 | sourceTree = ""; 112 | }; 113 | 17E4C4761A0122A700E61ACD = { 114 | isa = PBXGroup; 115 | children = ( 116 | 17E4C4A81A01235200E61ACD /* LLSimpleCamera */, 117 | 17E4C4811A0122A700E61ACD /* LLSimpleCameraExample */, 118 | 17E4C49B1A0122A700E61ACD /* LLSimpleCameraExampleTests */, 119 | 17E4C4801A0122A700E61ACD /* Products */, 120 | ); 121 | sourceTree = ""; 122 | }; 123 | 17E4C4801A0122A700E61ACD /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 17E4C47F1A0122A700E61ACD /* LLSimpleCameraExample.app */, 127 | 17E4C4981A0122A700E61ACD /* LLSimpleCameraExampleTests.xctest */, 128 | ); 129 | name = Products; 130 | sourceTree = ""; 131 | }; 132 | 17E4C4811A0122A700E61ACD /* LLSimpleCameraExample */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 17BD92411A17DB150016070B /* Images */, 136 | 17E4C4B61A0123E400E61ACD /* Controllers */, 137 | 17E4C4AC1A01238D00E61ACD /* Utility */, 138 | 17E4C4861A0122A700E61ACD /* AppDelegate.h */, 139 | 17E4C4871A0122A700E61ACD /* AppDelegate.m */, 140 | 17E4C48F1A0122A700E61ACD /* Images.xcassets */, 141 | 17E4C4911A0122A700E61ACD /* LaunchScreen.xib */, 142 | 17E4C4821A0122A700E61ACD /* Supporting Files */, 143 | ); 144 | path = LLSimpleCameraExample; 145 | sourceTree = ""; 146 | }; 147 | 17E4C4821A0122A700E61ACD /* Supporting Files */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 17E4C4831A0122A700E61ACD /* Info.plist */, 151 | 17E4C4841A0122A700E61ACD /* main.m */, 152 | ); 153 | name = "Supporting Files"; 154 | sourceTree = ""; 155 | }; 156 | 17E4C49B1A0122A700E61ACD /* LLSimpleCameraExampleTests */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 17E4C49E1A0122A700E61ACD /* LLSimpleCameraExampleTests.m */, 160 | 17E4C49C1A0122A700E61ACD /* Supporting Files */, 161 | ); 162 | path = LLSimpleCameraExampleTests; 163 | sourceTree = ""; 164 | }; 165 | 17E4C49C1A0122A700E61ACD /* Supporting Files */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 17E4C49D1A0122A700E61ACD /* Info.plist */, 169 | ); 170 | name = "Supporting Files"; 171 | sourceTree = ""; 172 | }; 173 | 17E4C4A81A01235200E61ACD /* LLSimpleCamera */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 171362191A0144100034A6FE /* UIImage+FixOrientation.h */, 177 | 1713621A1A0144100034A6FE /* UIImage+FixOrientation.m */, 178 | 17E4C4A91A01235200E61ACD /* LLSimpleCamera.h */, 179 | 17E4C4AA1A01235200E61ACD /* LLSimpleCamera.m */, 180 | 1733A8EC1C791BA5000B3648 /* LLSimpleCamera+Helper.h */, 181 | 1733A8ED1C791BA5000B3648 /* LLSimpleCamera+Helper.m */, 182 | ); 183 | path = LLSimpleCamera; 184 | sourceTree = ""; 185 | }; 186 | 17E4C4AC1A01238D00E61ACD /* Utility */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 17E4C4BA1A0128EC00E61ACD /* ViewUtils.h */, 190 | 17E4C4BB1A0128EC00E61ACD /* ViewUtils.m */, 191 | 17E4C4AD1A0123C100E61ACD /* UIImage+Crop.h */, 192 | 17E4C4AE1A0123C100E61ACD /* UIImage+Crop.m */, 193 | 17E4C4B11A0123C100E61ACD /* UIImage+Resize.h */, 194 | 17E4C4B21A0123C100E61ACD /* UIImage+Resize.m */, 195 | ); 196 | name = Utility; 197 | sourceTree = ""; 198 | }; 199 | 17E4C4B61A0123E400E61ACD /* Controllers */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | 170030CF1B122F3700585CEB /* VideoViewController.h */, 203 | 170030D01B122F3700585CEB /* VideoViewController.m */, 204 | 17E4C4B71A01285800E61ACD /* HomeViewController.h */, 205 | 17E4C4B81A01285800E61ACD /* HomeViewController.m */, 206 | 17BD92321A1776E20016070B /* ImageViewController.h */, 207 | 17BD92331A1776E20016070B /* ImageViewController.m */, 208 | ); 209 | name = Controllers; 210 | sourceTree = ""; 211 | }; 212 | /* End PBXGroup section */ 213 | 214 | /* Begin PBXNativeTarget section */ 215 | 17E4C47E1A0122A700E61ACD /* LLSimpleCameraExample */ = { 216 | isa = PBXNativeTarget; 217 | buildConfigurationList = 17E4C4A21A0122A700E61ACD /* Build configuration list for PBXNativeTarget "LLSimpleCameraExample" */; 218 | buildPhases = ( 219 | 17E4C47B1A0122A700E61ACD /* Sources */, 220 | 17E4C47C1A0122A700E61ACD /* Frameworks */, 221 | 17E4C47D1A0122A700E61ACD /* Resources */, 222 | ); 223 | buildRules = ( 224 | ); 225 | dependencies = ( 226 | ); 227 | name = LLSimpleCameraExample; 228 | productName = LLSimpleCameraExample; 229 | productReference = 17E4C47F1A0122A700E61ACD /* LLSimpleCameraExample.app */; 230 | productType = "com.apple.product-type.application"; 231 | }; 232 | 17E4C4971A0122A700E61ACD /* LLSimpleCameraExampleTests */ = { 233 | isa = PBXNativeTarget; 234 | buildConfigurationList = 17E4C4A51A0122A700E61ACD /* Build configuration list for PBXNativeTarget "LLSimpleCameraExampleTests" */; 235 | buildPhases = ( 236 | 17E4C4941A0122A700E61ACD /* Sources */, 237 | 17E4C4951A0122A700E61ACD /* Frameworks */, 238 | 17E4C4961A0122A700E61ACD /* Resources */, 239 | ); 240 | buildRules = ( 241 | ); 242 | dependencies = ( 243 | 17E4C49A1A0122A700E61ACD /* PBXTargetDependency */, 244 | ); 245 | name = LLSimpleCameraExampleTests; 246 | productName = LLSimpleCameraExampleTests; 247 | productReference = 17E4C4981A0122A700E61ACD /* LLSimpleCameraExampleTests.xctest */; 248 | productType = "com.apple.product-type.bundle.unit-test"; 249 | }; 250 | /* End PBXNativeTarget section */ 251 | 252 | /* Begin PBXProject section */ 253 | 17E4C4771A0122A700E61ACD /* Project object */ = { 254 | isa = PBXProject; 255 | attributes = { 256 | LastUpgradeCheck = 0600; 257 | ORGANIZATIONNAME = "Ömer Faruk Gül"; 258 | TargetAttributes = { 259 | 17E4C47E1A0122A700E61ACD = { 260 | CreatedOnToolsVersion = 6.0.1; 261 | DevelopmentTeam = NZPAC7Q696; 262 | }; 263 | 17E4C4971A0122A700E61ACD = { 264 | CreatedOnToolsVersion = 6.0.1; 265 | TestTargetID = 17E4C47E1A0122A700E61ACD; 266 | }; 267 | }; 268 | }; 269 | buildConfigurationList = 17E4C47A1A0122A700E61ACD /* Build configuration list for PBXProject "LLSimpleCameraExample" */; 270 | compatibilityVersion = "Xcode 3.2"; 271 | developmentRegion = English; 272 | hasScannedForEncodings = 0; 273 | knownRegions = ( 274 | en, 275 | Base, 276 | ); 277 | mainGroup = 17E4C4761A0122A700E61ACD; 278 | productRefGroup = 17E4C4801A0122A700E61ACD /* Products */; 279 | projectDirPath = ""; 280 | projectRoot = ""; 281 | targets = ( 282 | 17E4C47E1A0122A700E61ACD /* LLSimpleCameraExample */, 283 | 17E4C4971A0122A700E61ACD /* LLSimpleCameraExampleTests */, 284 | ); 285 | }; 286 | /* End PBXProject section */ 287 | 288 | /* Begin PBXResourcesBuildPhase section */ 289 | 17E4C47D1A0122A700E61ACD /* Resources */ = { 290 | isa = PBXResourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 170030D61B1231B400585CEB /* cancel@2x.png in Resources */, 294 | 17BD92641A17F0040016070B /* camera-switch.png in Resources */, 295 | 17BD92651A17F0040016070B /* camera-switch@2x.png in Resources */, 296 | 170030DB1B1232D100585CEB /* camera-flash@2x.png in Resources */, 297 | 17E4C4931A0122A700E61ACD /* LaunchScreen.xib in Resources */, 298 | 170030D51B1231B400585CEB /* cancel.png in Resources */, 299 | 17E4C4901A0122A700E61ACD /* Images.xcassets in Resources */, 300 | 170030D71B1231B400585CEB /* cancel@3x.png in Resources */, 301 | 170030DA1B1232D100585CEB /* camera-flash.png in Resources */, 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | 17E4C4961A0122A700E61ACD /* Resources */ = { 306 | isa = PBXResourcesBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | /* End PBXResourcesBuildPhase section */ 313 | 314 | /* Begin PBXSourcesBuildPhase section */ 315 | 17E4C47B1A0122A700E61ACD /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | 170030D11B122F3700585CEB /* VideoViewController.m in Sources */, 320 | 17BD92341A1776E20016070B /* ImageViewController.m in Sources */, 321 | 17E4C4AB1A01235200E61ACD /* LLSimpleCamera.m in Sources */, 322 | 17E4C4B31A0123C100E61ACD /* UIImage+Crop.m in Sources */, 323 | 1713621B1A0144100034A6FE /* UIImage+FixOrientation.m in Sources */, 324 | 17E4C4BC1A0128EC00E61ACD /* ViewUtils.m in Sources */, 325 | 17E4C4881A0122A700E61ACD /* AppDelegate.m in Sources */, 326 | 17E4C4851A0122A700E61ACD /* main.m in Sources */, 327 | 17E4C4B91A01285800E61ACD /* HomeViewController.m in Sources */, 328 | 1733A8EE1C791BA5000B3648 /* LLSimpleCamera+Helper.m in Sources */, 329 | 17E4C4B51A0123C100E61ACD /* UIImage+Resize.m in Sources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | 17E4C4941A0122A700E61ACD /* Sources */ = { 334 | isa = PBXSourcesBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | 17E4C49F1A0122A700E61ACD /* LLSimpleCameraExampleTests.m in Sources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | /* End PBXSourcesBuildPhase section */ 342 | 343 | /* Begin PBXTargetDependency section */ 344 | 17E4C49A1A0122A700E61ACD /* PBXTargetDependency */ = { 345 | isa = PBXTargetDependency; 346 | target = 17E4C47E1A0122A700E61ACD /* LLSimpleCameraExample */; 347 | targetProxy = 17E4C4991A0122A700E61ACD /* PBXContainerItemProxy */; 348 | }; 349 | /* End PBXTargetDependency section */ 350 | 351 | /* Begin PBXVariantGroup section */ 352 | 17E4C4911A0122A700E61ACD /* LaunchScreen.xib */ = { 353 | isa = PBXVariantGroup; 354 | children = ( 355 | 17E4C4921A0122A700E61ACD /* Base */, 356 | ); 357 | name = LaunchScreen.xib; 358 | sourceTree = ""; 359 | }; 360 | /* End PBXVariantGroup section */ 361 | 362 | /* Begin XCBuildConfiguration section */ 363 | 17E4C4A01A0122A700E61ACD /* Debug */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ALWAYS_SEARCH_USER_PATHS = NO; 367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 368 | CLANG_CXX_LIBRARY = "libc++"; 369 | CLANG_ENABLE_MODULES = YES; 370 | CLANG_ENABLE_OBJC_ARC = YES; 371 | CLANG_WARN_BOOL_CONVERSION = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 374 | CLANG_WARN_EMPTY_BODY = YES; 375 | CLANG_WARN_ENUM_CONVERSION = YES; 376 | CLANG_WARN_INT_CONVERSION = YES; 377 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 378 | CLANG_WARN_UNREACHABLE_CODE = YES; 379 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 380 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 381 | COPY_PHASE_STRIP = NO; 382 | ENABLE_STRICT_OBJC_MSGSEND = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu99; 384 | GCC_DYNAMIC_NO_PIC = NO; 385 | GCC_OPTIMIZATION_LEVEL = 0; 386 | GCC_PREPROCESSOR_DEFINITIONS = ( 387 | "DEBUG=1", 388 | "$(inherited)", 389 | ); 390 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 391 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 392 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 393 | GCC_WARN_UNDECLARED_SELECTOR = YES; 394 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 395 | GCC_WARN_UNUSED_FUNCTION = YES; 396 | GCC_WARN_UNUSED_VARIABLE = YES; 397 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 398 | MTL_ENABLE_DEBUG_INFO = YES; 399 | ONLY_ACTIVE_ARCH = YES; 400 | SDKROOT = iphoneos; 401 | }; 402 | name = Debug; 403 | }; 404 | 17E4C4A11A0122A700E61ACD /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ALWAYS_SEARCH_USER_PATHS = NO; 408 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 409 | CLANG_CXX_LIBRARY = "libc++"; 410 | CLANG_ENABLE_MODULES = YES; 411 | CLANG_ENABLE_OBJC_ARC = YES; 412 | CLANG_WARN_BOOL_CONVERSION = YES; 413 | CLANG_WARN_CONSTANT_CONVERSION = YES; 414 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 415 | CLANG_WARN_EMPTY_BODY = YES; 416 | CLANG_WARN_ENUM_CONVERSION = YES; 417 | CLANG_WARN_INT_CONVERSION = YES; 418 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 419 | CLANG_WARN_UNREACHABLE_CODE = YES; 420 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 421 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 422 | COPY_PHASE_STRIP = YES; 423 | ENABLE_NS_ASSERTIONS = NO; 424 | ENABLE_STRICT_OBJC_MSGSEND = YES; 425 | GCC_C_LANGUAGE_STANDARD = gnu99; 426 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 427 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 428 | GCC_WARN_UNDECLARED_SELECTOR = YES; 429 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 430 | GCC_WARN_UNUSED_FUNCTION = YES; 431 | GCC_WARN_UNUSED_VARIABLE = YES; 432 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 433 | MTL_ENABLE_DEBUG_INFO = NO; 434 | SDKROOT = iphoneos; 435 | VALIDATE_PRODUCT = YES; 436 | }; 437 | name = Release; 438 | }; 439 | 17E4C4A31A0122A700E61ACD /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 443 | CODE_SIGN_IDENTITY = "iPhone Developer"; 444 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 445 | INFOPLIST_FILE = LLSimpleCameraExample/Info.plist; 446 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 447 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | PROVISIONING_PROFILE = ""; 450 | }; 451 | name = Debug; 452 | }; 453 | 17E4C4A41A0122A700E61ACD /* Release */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | CODE_SIGN_IDENTITY = "iPhone Developer"; 458 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 459 | INFOPLIST_FILE = LLSimpleCameraExample/Info.plist; 460 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | PROVISIONING_PROFILE = ""; 464 | }; 465 | name = Release; 466 | }; 467 | 17E4C4A61A0122A700E61ACD /* Debug */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | BUNDLE_LOADER = "$(TEST_HOST)"; 471 | FRAMEWORK_SEARCH_PATHS = ( 472 | "$(SDKROOT)/Developer/Library/Frameworks", 473 | "$(inherited)", 474 | ); 475 | GCC_PREPROCESSOR_DEFINITIONS = ( 476 | "DEBUG=1", 477 | "$(inherited)", 478 | ); 479 | INFOPLIST_FILE = LLSimpleCameraExampleTests/Info.plist; 480 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LLSimpleCameraExample.app/LLSimpleCameraExample"; 483 | }; 484 | name = Debug; 485 | }; 486 | 17E4C4A71A0122A700E61ACD /* Release */ = { 487 | isa = XCBuildConfiguration; 488 | buildSettings = { 489 | BUNDLE_LOADER = "$(TEST_HOST)"; 490 | FRAMEWORK_SEARCH_PATHS = ( 491 | "$(SDKROOT)/Developer/Library/Frameworks", 492 | "$(inherited)", 493 | ); 494 | INFOPLIST_FILE = LLSimpleCameraExampleTests/Info.plist; 495 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LLSimpleCameraExample.app/LLSimpleCameraExample"; 498 | }; 499 | name = Release; 500 | }; 501 | /* End XCBuildConfiguration section */ 502 | 503 | /* Begin XCConfigurationList section */ 504 | 17E4C47A1A0122A700E61ACD /* Build configuration list for PBXProject "LLSimpleCameraExample" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | 17E4C4A01A0122A700E61ACD /* Debug */, 508 | 17E4C4A11A0122A700E61ACD /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | 17E4C4A21A0122A700E61ACD /* Build configuration list for PBXNativeTarget "LLSimpleCameraExample" */ = { 514 | isa = XCConfigurationList; 515 | buildConfigurations = ( 516 | 17E4C4A31A0122A700E61ACD /* Debug */, 517 | 17E4C4A41A0122A700E61ACD /* Release */, 518 | ); 519 | defaultConfigurationIsVisible = 0; 520 | defaultConfigurationName = Release; 521 | }; 522 | 17E4C4A51A0122A700E61ACD /* Build configuration list for PBXNativeTarget "LLSimpleCameraExampleTests" */ = { 523 | isa = XCConfigurationList; 524 | buildConfigurations = ( 525 | 17E4C4A61A0122A700E61ACD /* Debug */, 526 | 17E4C4A71A0122A700E61ACD /* Release */, 527 | ); 528 | defaultConfigurationIsVisible = 0; 529 | defaultConfigurationName = Release; 530 | }; 531 | /* End XCConfigurationList section */ 532 | }; 533 | rootObject = 17E4C4771A0122A700E61ACD /* Project object */; 534 | } 535 | -------------------------------------------------------------------------------- /LLSimpleCameraExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 29/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class HomeViewController; 12 | 13 | @interface AppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | @property (strong, nonatomic) HomeViewController *homeVC; 17 | 18 | @end 19 | 20 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 29/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "HomeViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | 21 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 22 | 23 | self.homeVC = [[HomeViewController alloc] initWithNibName:nil bundle:nil]; 24 | UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:self.homeVC]; 25 | 26 | self.window.rootViewController = navVC; 27 | 28 | [self.window makeKeyAndVisible]; 29 | return YES; 30 | } 31 | 32 | - (void)applicationWillResignActive:(UIApplication *)application { 33 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 34 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 35 | } 36 | 37 | - (void)applicationDidEnterBackground:(UIApplication *)application { 38 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 39 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 40 | } 41 | 42 | - (void)applicationWillEnterForeground:(UIApplication *)application { 43 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 44 | } 45 | 46 | - (void)applicationDidBecomeActive:(UIApplication *)application { 47 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 48 | } 49 | 50 | - (void)applicationWillTerminate:(UIApplication *)application { 51 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/HomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.h 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 29/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LLSimpleCamera.h" 11 | 12 | @interface HomeViewController : UIViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/HomeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.m 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 29/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | #import "ViewUtils.h" 11 | #import "ImageViewController.h" 12 | #import "VideoViewController.h" 13 | 14 | @interface HomeViewController () 15 | @property (strong, nonatomic) LLSimpleCamera *camera; 16 | @property (strong, nonatomic) UILabel *errorLabel; 17 | @property (strong, nonatomic) UIButton *snapButton; 18 | @property (strong, nonatomic) UIButton *switchButton; 19 | @property (strong, nonatomic) UIButton *flashButton; 20 | @property (strong, nonatomic) UISegmentedControl *segmentedControl; 21 | @end 22 | 23 | @implementation HomeViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | 29 | self.view.backgroundColor = [UIColor blackColor]; 30 | [self.navigationController setNavigationBarHidden:YES animated:NO]; 31 | 32 | CGRect screenRect = [[UIScreen mainScreen] bounds]; 33 | 34 | // ----- initialize camera -------- // 35 | 36 | // create camera vc 37 | self.camera = [[LLSimpleCamera alloc] initWithQuality:AVCaptureSessionPresetHigh 38 | position:LLCameraPositionRear 39 | videoEnabled:YES]; 40 | 41 | // attach to a view controller 42 | [self.camera attachToViewController:self withFrame:CGRectMake(0, 0, screenRect.size.width, screenRect.size.height)]; 43 | 44 | // read: http://stackoverflow.com/questions/5427656/ios-uiimagepickercontroller-result-image-orientation-after-upload 45 | // you probably will want to set this to YES, if you are going view the image outside iOS. 46 | self.camera.fixOrientationAfterCapture = NO; 47 | 48 | // take the required actions on a device change 49 | __weak typeof(self) weakSelf = self; 50 | [self.camera setOnDeviceChange:^(LLSimpleCamera *camera, AVCaptureDevice * device) { 51 | 52 | NSLog(@"Device changed."); 53 | 54 | // device changed, check if flash is available 55 | if([camera isFlashAvailable]) { 56 | weakSelf.flashButton.hidden = NO; 57 | 58 | if(camera.flash == LLCameraFlashOff) { 59 | weakSelf.flashButton.selected = NO; 60 | } 61 | else { 62 | weakSelf.flashButton.selected = YES; 63 | } 64 | } 65 | else { 66 | weakSelf.flashButton.hidden = YES; 67 | } 68 | }]; 69 | 70 | [self.camera setOnError:^(LLSimpleCamera *camera, NSError *error) { 71 | NSLog(@"Camera error: %@", error); 72 | 73 | if([error.domain isEqualToString:LLSimpleCameraErrorDomain]) { 74 | if(error.code == LLSimpleCameraErrorCodeCameraPermission || 75 | error.code == LLSimpleCameraErrorCodeMicrophonePermission) { 76 | 77 | if(weakSelf.errorLabel) { 78 | [weakSelf.errorLabel removeFromSuperview]; 79 | } 80 | 81 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; 82 | label.text = @"We need permission for the camera.\nPlease go to your settings."; 83 | label.numberOfLines = 2; 84 | label.lineBreakMode = NSLineBreakByWordWrapping; 85 | label.backgroundColor = [UIColor clearColor]; 86 | label.font = [UIFont fontWithName:@"AvenirNext-DemiBold" size:13.0f]; 87 | label.textColor = [UIColor whiteColor]; 88 | label.textAlignment = NSTextAlignmentCenter; 89 | [label sizeToFit]; 90 | label.center = CGPointMake(screenRect.size.width / 2.0f, screenRect.size.height / 2.0f); 91 | weakSelf.errorLabel = label; 92 | [weakSelf.view addSubview:weakSelf.errorLabel]; 93 | } 94 | } 95 | }]; 96 | 97 | // ----- camera buttons -------- // 98 | 99 | // snap button to capture image 100 | self.snapButton = [UIButton buttonWithType:UIButtonTypeCustom]; 101 | self.snapButton.frame = CGRectMake(0, 0, 70.0f, 70.0f); 102 | self.snapButton.clipsToBounds = YES; 103 | self.snapButton.layer.cornerRadius = self.snapButton.width / 2.0f; 104 | self.snapButton.layer.borderColor = [UIColor whiteColor].CGColor; 105 | self.snapButton.layer.borderWidth = 2.0f; 106 | self.snapButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5]; 107 | self.snapButton.layer.rasterizationScale = [UIScreen mainScreen].scale; 108 | self.snapButton.layer.shouldRasterize = YES; 109 | [self.snapButton addTarget:self action:@selector(snapButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 110 | [self.view addSubview:self.snapButton]; 111 | 112 | // button to toggle flash 113 | self.flashButton = [UIButton buttonWithType:UIButtonTypeSystem]; 114 | self.flashButton.frame = CGRectMake(0, 0, 16.0f + 20.0f, 24.0f + 20.0f); 115 | self.flashButton.tintColor = [UIColor whiteColor]; 116 | [self.flashButton setImage:[UIImage imageNamed:@"camera-flash.png"] forState:UIControlStateNormal]; 117 | self.flashButton.imageEdgeInsets = UIEdgeInsetsMake(10.0f, 10.0f, 10.0f, 10.0f); 118 | [self.flashButton addTarget:self action:@selector(flashButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 119 | [self.view addSubview:self.flashButton]; 120 | 121 | if([LLSimpleCamera isFrontCameraAvailable] && [LLSimpleCamera isRearCameraAvailable]) { 122 | // button to toggle camera positions 123 | self.switchButton = [UIButton buttonWithType:UIButtonTypeSystem]; 124 | self.switchButton.frame = CGRectMake(0, 0, 29.0f + 20.0f, 22.0f + 20.0f); 125 | self.switchButton.tintColor = [UIColor whiteColor]; 126 | [self.switchButton setImage:[UIImage imageNamed:@"camera-switch.png"] forState:UIControlStateNormal]; 127 | self.switchButton.imageEdgeInsets = UIEdgeInsetsMake(10.0f, 10.0f, 10.0f, 10.0f); 128 | [self.switchButton addTarget:self action:@selector(switchButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 129 | [self.view addSubview:self.switchButton]; 130 | } 131 | 132 | self.segmentedControl = [[UISegmentedControl alloc] initWithItems:@[@"Picture",@"Video"]]; 133 | self.segmentedControl.frame = CGRectMake(12.0f, screenRect.size.height - 67.0f, 120.0f, 32.0f); 134 | self.segmentedControl.selectedSegmentIndex = 0; 135 | self.segmentedControl.tintColor = [UIColor whiteColor]; 136 | [self.segmentedControl addTarget:self action:@selector(segmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged]; 137 | [self.view addSubview:self.segmentedControl]; 138 | } 139 | 140 | - (void)segmentedControlValueChanged:(UISegmentedControl *)control 141 | { 142 | NSLog(@"Segment value changed!"); 143 | } 144 | 145 | - (void)viewWillAppear:(BOOL)animated 146 | { 147 | [super viewWillAppear:animated]; 148 | 149 | // start the camera 150 | [self.camera start]; 151 | } 152 | 153 | /* camera button methods */ 154 | 155 | - (void)switchButtonPressed:(UIButton *)button 156 | { 157 | [self.camera togglePosition]; 158 | } 159 | 160 | - (NSURL *)applicationDocumentsDirectory 161 | { 162 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 163 | } 164 | 165 | - (void)flashButtonPressed:(UIButton *)button 166 | { 167 | if(self.camera.flash == LLCameraFlashOff) { 168 | BOOL done = [self.camera updateFlashMode:LLCameraFlashOn]; 169 | if(done) { 170 | self.flashButton.selected = YES; 171 | self.flashButton.tintColor = [UIColor yellowColor]; 172 | } 173 | } 174 | else { 175 | BOOL done = [self.camera updateFlashMode:LLCameraFlashOff]; 176 | if(done) { 177 | self.flashButton.selected = NO; 178 | self.flashButton.tintColor = [UIColor whiteColor]; 179 | } 180 | } 181 | } 182 | 183 | - (void)snapButtonPressed:(UIButton *)button 184 | { 185 | __weak typeof(self) weakSelf = self; 186 | 187 | if(self.segmentedControl.selectedSegmentIndex == 0) { 188 | // capture 189 | [self.camera capture:^(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error) { 190 | if(!error) { 191 | ImageViewController *imageVC = [[ImageViewController alloc] initWithImage:image]; 192 | [weakSelf presentViewController:imageVC animated:NO completion:nil]; 193 | } 194 | else { 195 | NSLog(@"An error has occured: %@", error); 196 | } 197 | } exactSeenImage:YES]; 198 | 199 | } else { 200 | if(!self.camera.isRecording) { 201 | self.segmentedControl.hidden = YES; 202 | self.flashButton.hidden = YES; 203 | self.switchButton.hidden = YES; 204 | 205 | self.snapButton.layer.borderColor = [UIColor redColor].CGColor; 206 | self.snapButton.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.5]; 207 | 208 | // start recording 209 | NSURL *outputURL = [[[self applicationDocumentsDirectory] 210 | URLByAppendingPathComponent:@"test1"] URLByAppendingPathExtension:@"mov"]; 211 | [self.camera startRecordingWithOutputUrl:outputURL didRecord:^(LLSimpleCamera *camera, NSURL *outputFileUrl, NSError *error) { 212 | VideoViewController *vc = [[VideoViewController alloc] initWithVideoUrl:outputFileUrl]; 213 | [self.navigationController pushViewController:vc animated:YES]; 214 | }]; 215 | 216 | } else { 217 | self.segmentedControl.hidden = NO; 218 | self.flashButton.hidden = NO; 219 | self.switchButton.hidden = NO; 220 | 221 | self.snapButton.layer.borderColor = [UIColor whiteColor].CGColor; 222 | self.snapButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5]; 223 | 224 | [self.camera stopRecording]; 225 | } 226 | } 227 | } 228 | 229 | /* other lifecycle methods */ 230 | 231 | - (void)viewWillLayoutSubviews 232 | { 233 | [super viewWillLayoutSubviews]; 234 | 235 | self.camera.view.frame = self.view.contentBounds; 236 | 237 | self.snapButton.center = self.view.contentCenter; 238 | self.snapButton.bottom = self.view.height - 15.0f; 239 | 240 | self.flashButton.center = self.view.contentCenter; 241 | self.flashButton.top = 5.0f; 242 | 243 | self.switchButton.top = 5.0f; 244 | self.switchButton.right = self.view.width - 5.0f; 245 | 246 | self.segmentedControl.left = 12.0f; 247 | self.segmentedControl.bottom = self.view.height - 35.0f; 248 | } 249 | 250 | - (BOOL)prefersStatusBarHidden 251 | { 252 | return YES; 253 | } 254 | 255 | - (UIInterfaceOrientation) preferredInterfaceOrientationForPresentation 256 | { 257 | return UIInterfaceOrientationPortrait; 258 | } 259 | 260 | - (void)didReceiveMemoryWarning 261 | { 262 | [super didReceiveMemoryWarning]; 263 | } 264 | 265 | @end 266 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/ImageViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewController.h 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 15/11/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ImageViewController : UIViewController 12 | - (instancetype)initWithImage:(UIImage *)image; 13 | @end 14 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/ImageViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewController.m 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 15/11/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import "ImageViewController.h" 10 | #import "ViewUtils.h" 11 | #import "UIImage+Crop.h" 12 | 13 | @interface ImageViewController () 14 | @property (strong, nonatomic) UIImage *image; 15 | @property (strong, nonatomic) UIImageView *imageView; 16 | @property (strong, nonatomic) UILabel *infoLabel; 17 | @property (strong, nonatomic) UIButton *cancelButton; 18 | @end 19 | 20 | @implementation ImageViewController 21 | 22 | - (instancetype)initWithImage:(UIImage *)image { 23 | self = [super initWithNibName:nil bundle:nil]; 24 | if(self) { 25 | _image = image; 26 | } 27 | 28 | return self; 29 | } 30 | 31 | - (void)viewDidLoad { 32 | [super viewDidLoad]; 33 | 34 | self.imageView.backgroundColor = [UIColor blackColor]; 35 | 36 | CGRect screenRect = [[UIScreen mainScreen] bounds]; 37 | 38 | self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, screenRect.size.width, screenRect.size.height)]; 39 | self.imageView.contentMode = UIViewContentModeScaleAspectFit; 40 | self.imageView.backgroundColor = [UIColor clearColor]; 41 | self.imageView.image = self.image; 42 | [self.view addSubview:self.imageView]; 43 | 44 | NSString *info = [NSString stringWithFormat:@"Size: %@ - Orientation: %ld", NSStringFromCGSize(self.image.size), (long)self.image.imageOrientation]; 45 | 46 | self.infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 20)]; 47 | self.infoLabel.backgroundColor = [[UIColor darkGrayColor] colorWithAlphaComponent:0.7]; 48 | self.infoLabel.textColor = [UIColor whiteColor]; 49 | self.infoLabel.font = [UIFont fontWithName:@"AvenirNext-Regular" size:13]; 50 | self.infoLabel.textAlignment = NSTextAlignmentCenter; 51 | self.infoLabel.text = info; 52 | [self.view addSubview:self.infoLabel]; 53 | 54 | UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(viewTapped:)]; 55 | [self.view addGestureRecognizer:tapGesture]; 56 | } 57 | 58 | - (void)viewTapped:(UIGestureRecognizer *)gesture { 59 | [self dismissViewControllerAnimated:NO completion:nil]; 60 | } 61 | 62 | - (BOOL)prefersStatusBarHidden { 63 | return YES; 64 | } 65 | 66 | - (void)viewWillLayoutSubviews { 67 | [super viewWillLayoutSubviews]; 68 | 69 | self.imageView.frame = self.view.contentBounds; 70 | 71 | [self.infoLabel sizeToFit]; 72 | self.infoLabel.width = self.view.contentBounds.size.width; 73 | self.infoLabel.top = 0; 74 | self.infoLabel.left = 0; 75 | } 76 | 77 | - (void)didReceiveMemoryWarning { 78 | [super didReceiveMemoryWarning]; 79 | // Dispose of any resources that can be recreated. 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /LLSimpleCameraExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.omerfarukgul.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/UIImage+Crop.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Crop.h 3 | // LLSimpleCamera 4 | // 5 | // Created by Ömer Faruk Gül on 27/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage(CropCategory) 12 | - (UIImage *)crop:(CGRect)rect; 13 | @end 14 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/UIImage+Crop.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Crop.m 3 | // LLSimpleCamera 4 | // 5 | // Created by Ömer Faruk Gül on 27/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import "UIImage+Crop.h" 10 | 11 | @implementation UIImage(CropCategory) 12 | - (UIImage *)crop:(CGRect)rect { 13 | 14 | rect = CGRectMake(rect.origin.x * self.scale, 15 | rect.origin.y * self.scale, 16 | rect.size.width * self.scale, 17 | rect.size.height * self.scale); 18 | 19 | CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect); 20 | UIImage *result = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; 21 | CGImageRelease(imageRef); 22 | return result; 23 | } 24 | @end 25 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/UIImage+Resize.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************************** 2 | * 3 | * Copyright (c) 2010 Olivier Halligon 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | *********************************************************************************** 24 | * 25 | * Any comment or suggestion welcome. Referencing this project in your AboutBox is appreciated. 26 | * Please tell me if you use this class so we can cross-reference our projects. 27 | * 28 | ***********************************************************************************/ 29 | 30 | 31 | #import 32 | 33 | @interface UIImage(ResizeCategory) 34 | -(UIImage*)resizedImageToSize:(CGSize)dstSize; 35 | -(UIImage*)resizedImageToFitInSize:(CGSize)boundingSize scaleIfSmaller:(BOOL)scale; 36 | @end 37 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/UIImage+Resize.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Resize.m 3 | // 4 | // Created by Olivier Halligon on 12/08/09. 5 | // Copyright 2009 AliSoftware. All rights reserved. 6 | // 7 | 8 | #import "UIImage+Resize.h" 9 | 10 | @implementation UIImage (ResizeCategory) 11 | 12 | -(UIImage*)resizedImageToSize:(CGSize)dstSize 13 | { 14 | CGImageRef imgRef = self.CGImage; 15 | // the below values are regardless of orientation : for UIImages from Camera, width>height (landscape) 16 | CGSize srcSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef)); // not equivalent to self.size (which is dependant on the imageOrientation)! 17 | 18 | /* Don't resize if we already meet the required destination size. */ 19 | if (CGSizeEqualToSize(srcSize, dstSize)) { 20 | return self; 21 | } 22 | 23 | CGFloat scaleRatio = dstSize.width / srcSize.width; 24 | UIImageOrientation orient = self.imageOrientation; 25 | CGAffineTransform transform = CGAffineTransformIdentity; 26 | switch(orient) { 27 | 28 | case UIImageOrientationUp: //EXIF = 1 29 | transform = CGAffineTransformIdentity; 30 | break; 31 | 32 | case UIImageOrientationUpMirrored: //EXIF = 2 33 | transform = CGAffineTransformMakeTranslation(srcSize.width, 0.0); 34 | transform = CGAffineTransformScale(transform, -1.0, 1.0); 35 | break; 36 | 37 | case UIImageOrientationDown: //EXIF = 3 38 | transform = CGAffineTransformMakeTranslation(srcSize.width, srcSize.height); 39 | transform = CGAffineTransformRotate(transform, M_PI); 40 | break; 41 | 42 | case UIImageOrientationDownMirrored: //EXIF = 4 43 | transform = CGAffineTransformMakeTranslation(0.0, srcSize.height); 44 | transform = CGAffineTransformScale(transform, 1.0, -1.0); 45 | break; 46 | 47 | case UIImageOrientationLeftMirrored: //EXIF = 5 48 | dstSize = CGSizeMake(dstSize.height, dstSize.width); 49 | transform = CGAffineTransformMakeTranslation(srcSize.height, srcSize.width); 50 | transform = CGAffineTransformScale(transform, -1.0, 1.0); 51 | transform = CGAffineTransformRotate(transform, 3.0 * M_PI_2); 52 | break; 53 | 54 | case UIImageOrientationLeft: //EXIF = 6 55 | dstSize = CGSizeMake(dstSize.height, dstSize.width); 56 | transform = CGAffineTransformMakeTranslation(0.0, srcSize.width); 57 | transform = CGAffineTransformRotate(transform, 3.0 * M_PI_2); 58 | break; 59 | 60 | case UIImageOrientationRightMirrored: //EXIF = 7 61 | dstSize = CGSizeMake(dstSize.height, dstSize.width); 62 | transform = CGAffineTransformMakeScale(-1.0, 1.0); 63 | transform = CGAffineTransformRotate(transform, M_PI_2); 64 | break; 65 | 66 | case UIImageOrientationRight: //EXIF = 8 67 | dstSize = CGSizeMake(dstSize.height, dstSize.width); 68 | transform = CGAffineTransformMakeTranslation(srcSize.height, 0.0); 69 | transform = CGAffineTransformRotate(transform, M_PI_2); 70 | break; 71 | 72 | default: 73 | [NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"]; 74 | 75 | } 76 | 77 | ///////////////////////////////////////////////////////////////////////////// 78 | // The actual resize: draw the image on a new context, applying a transform matrix 79 | UIGraphicsBeginImageContextWithOptions(dstSize, NO, self.scale); 80 | 81 | CGContextRef context = UIGraphicsGetCurrentContext(); 82 | 83 | if (!context) { 84 | return nil; 85 | } 86 | 87 | if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) { 88 | CGContextScaleCTM(context, -scaleRatio, scaleRatio); 89 | CGContextTranslateCTM(context, -srcSize.height, 0); 90 | } else { 91 | CGContextScaleCTM(context, scaleRatio, -scaleRatio); 92 | CGContextTranslateCTM(context, 0, -srcSize.height); 93 | } 94 | 95 | CGContextConcatCTM(context, transform); 96 | 97 | // we use srcSize (and not dstSize) as the size to specify is in user space (and we use the CTM to apply a scaleRatio) 98 | CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, srcSize.width, srcSize.height), imgRef); 99 | UIImage* resizedImage = UIGraphicsGetImageFromCurrentImageContext(); 100 | UIGraphicsEndImageContext(); 101 | 102 | return resizedImage; 103 | } 104 | 105 | 106 | 107 | ///////////////////////////////////////////////////////////////////////////// 108 | 109 | 110 | 111 | -(UIImage*)resizedImageToFitInSize:(CGSize)boundingSize scaleIfSmaller:(BOOL)scale 112 | { 113 | // get the image size (independant of imageOrientation) 114 | CGImageRef imgRef = self.CGImage; 115 | CGSize srcSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef)); // not equivalent to self.size (which depends on the imageOrientation)! 116 | 117 | // adjust boundingSize to make it independant on imageOrientation too for farther computations 118 | UIImageOrientation orient = self.imageOrientation; 119 | switch (orient) { 120 | case UIImageOrientationLeft: 121 | case UIImageOrientationRight: 122 | case UIImageOrientationLeftMirrored: 123 | case UIImageOrientationRightMirrored: 124 | boundingSize = CGSizeMake(boundingSize.height, boundingSize.width); 125 | break; 126 | default: 127 | // NOP 128 | break; 129 | } 130 | 131 | // Compute the target CGRect in order to keep aspect-ratio 132 | CGSize dstSize; 133 | 134 | if ( !scale && (srcSize.width < boundingSize.width) && (srcSize.height < boundingSize.height) ) { 135 | //NSLog(@"Image is smaller, and we asked not to scale it in this case (scaleIfSmaller:NO)"); 136 | dstSize = srcSize; // no resize (we could directly return 'self' here, but we draw the image anyway to take image orientation into account) 137 | } else { 138 | CGFloat wRatio = boundingSize.width / srcSize.width; 139 | CGFloat hRatio = boundingSize.height / srcSize.height; 140 | 141 | if (wRatio < hRatio) { 142 | //NSLog(@"Width imposed, Height scaled ; ratio = %f",wRatio); 143 | dstSize = CGSizeMake(boundingSize.width, floorf(srcSize.height * wRatio)); 144 | } else { 145 | //NSLog(@"Height imposed, Width scaled ; ratio = %f",hRatio); 146 | dstSize = CGSizeMake(floorf(srcSize.width * hRatio), boundingSize.height); 147 | } 148 | } 149 | 150 | return [self resizedImageToSize:dstSize]; 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/VideoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestVideoViewController.h 3 | // Memento 4 | // 5 | // Created by Ömer Faruk Gül on 22/05/15. 6 | // Copyright (c) 2015 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VideoViewController : UIViewController 12 | - (instancetype)initWithVideoUrl:(NSURL *)url; 13 | @end 14 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/VideoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestVideoViewController.m 3 | // Memento 4 | // 5 | // Created by Ömer Faruk Gül on 22/05/15. 6 | // Copyright (c) 2015 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import "VideoViewController.h" 10 | @import AVFoundation; 11 | 12 | @interface VideoViewController () 13 | @property (strong, nonatomic) NSURL *videoUrl; 14 | @property (strong, nonatomic) AVPlayer *avPlayer; 15 | @property (strong, nonatomic) AVPlayerLayer *avPlayerLayer; 16 | @property (strong, nonatomic) UIButton *cancelButton; 17 | @end 18 | 19 | @implementation VideoViewController 20 | 21 | - (instancetype)initWithVideoUrl:(NSURL *)url { 22 | self = [super init]; 23 | if(self) { 24 | _videoUrl = url; 25 | } 26 | 27 | return self; 28 | } 29 | 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | 33 | self.view.backgroundColor = [UIColor whiteColor]; 34 | 35 | // the video player 36 | self.avPlayer = [AVPlayer playerWithURL:self.videoUrl]; 37 | self.avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 38 | 39 | self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; 40 | //self.avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 41 | 42 | [[NSNotificationCenter defaultCenter] addObserver:self 43 | selector:@selector(playerItemDidReachEnd:) 44 | name:AVPlayerItemDidPlayToEndTimeNotification 45 | object:[self.avPlayer currentItem]]; 46 | 47 | CGRect screenRect = [[UIScreen mainScreen] bounds]; 48 | 49 | self.avPlayerLayer.frame = CGRectMake(0, 0, screenRect.size.width, screenRect.size.height); 50 | [self.view.layer addSublayer:self.avPlayerLayer]; 51 | 52 | // cancel button 53 | [self.view addSubview:self.cancelButton]; 54 | [self.cancelButton addTarget:self action:@selector(cancelButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 55 | self.cancelButton.frame = CGRectMake(0, 0, 44, 44); 56 | } 57 | 58 | - (void)viewWillAppear:(BOOL)animated{ 59 | [super viewWillAppear:animated]; 60 | 61 | [self.avPlayer play]; 62 | } 63 | 64 | - (void)playerItemDidReachEnd:(NSNotification *)notification { 65 | AVPlayerItem *p = [notification object]; 66 | [p seekToTime:kCMTimeZero]; 67 | } 68 | 69 | - (BOOL)prefersStatusBarHidden { 70 | return YES; 71 | } 72 | 73 | - (UIButton *)cancelButton { 74 | if(!_cancelButton) { 75 | UIImage *cancelImage = [UIImage imageNamed:@"cancel.png"]; 76 | UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; 77 | button.tintColor = [UIColor whiteColor]; 78 | [button setImage:cancelImage forState:UIControlStateNormal]; 79 | button.imageView.clipsToBounds = NO; 80 | button.contentEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10); 81 | button.layer.shadowColor = [UIColor blackColor].CGColor; 82 | button.layer.shadowOffset = CGSizeMake(0.0f, 0.0f); 83 | button.layer.shadowOpacity = 0.4f; 84 | button.layer.shadowRadius = 1.0f; 85 | button.clipsToBounds = NO; 86 | 87 | _cancelButton = button; 88 | } 89 | 90 | return _cancelButton; 91 | } 92 | 93 | - (void)cancelButtonPressed:(UIButton *)button { 94 | NSLog(@"cancel button pressed!"); 95 | [self.navigationController popViewControllerAnimated:YES]; 96 | } 97 | 98 | - (void)didReceiveMemoryWarning { 99 | [super didReceiveMemoryWarning]; 100 | // Dispose of any resources that can be recreated. 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/ViewUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewUtils.h 3 | // 4 | // Version 1.1.2 5 | // 6 | // Created by Nick Lockwood on 19/11/2011. 7 | // Copyright (c) 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/ViewUtils 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #import 35 | 36 | @interface UIView (ViewUtils) 37 | 38 | //nib loading 39 | 40 | + (id)instanceWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil owner:(id)owner; 41 | - (void)loadContentsWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil; 42 | 43 | //hierarchy 44 | 45 | - (UIView *)viewMatchingPredicate:(NSPredicate *)predicate; 46 | - (UIView *)viewWithTag:(NSInteger)tag ofClass:(Class)viewClass; 47 | - (UIView *)viewOfClass:(Class)viewClass; 48 | - (NSArray *)viewsMatchingPredicate:(NSPredicate *)predicate; 49 | - (NSArray *)viewsWithTag:(NSInteger)tag; 50 | - (NSArray *)viewsWithTag:(NSInteger)tag ofClass:(Class)viewClass; 51 | - (NSArray *)viewsOfClass:(Class)viewClass; 52 | 53 | - (UIView *)firstSuperviewMatchingPredicate:(NSPredicate *)predicate; 54 | - (UIView *)firstSuperviewOfClass:(Class)viewClass; 55 | - (UIView *)firstSuperviewWithTag:(NSInteger)tag; 56 | - (UIView *)firstSuperviewWithTag:(NSInteger)tag ofClass:(Class)viewClass; 57 | 58 | - (BOOL)viewOrAnySuperviewMatchesPredicate:(NSPredicate *)predicate; 59 | - (BOOL)viewOrAnySuperviewIsKindOfClass:(Class)viewClass; 60 | - (BOOL)isSuperviewOfView:(UIView *)view; 61 | - (BOOL)isSubviewOfView:(UIView *)view; 62 | 63 | - (UIViewController *)firstViewController; 64 | - (UIView *)firstResponder; 65 | 66 | //frame accessors 67 | 68 | @property (nonatomic, assign) CGPoint origin; 69 | @property (nonatomic, assign) CGSize size; 70 | @property (nonatomic, assign) CGFloat top; 71 | @property (nonatomic, assign) CGFloat left; 72 | @property (nonatomic, assign) CGFloat bottom; 73 | @property (nonatomic, assign) CGFloat right; 74 | @property (nonatomic, assign) CGFloat width; 75 | @property (nonatomic, assign) CGFloat height; 76 | @property (nonatomic, assign) CGFloat x; 77 | @property (nonatomic, assign) CGFloat y; 78 | 79 | //bounds accessors 80 | 81 | @property (nonatomic, assign) CGSize boundsSize; 82 | @property (nonatomic, assign) CGFloat boundsWidth; 83 | @property (nonatomic, assign) CGFloat boundsHeight; 84 | 85 | //content getters 86 | 87 | @property (nonatomic, readonly) CGRect contentBounds; 88 | @property (nonatomic, readonly) CGPoint contentCenter; 89 | 90 | //additional frame setters 91 | 92 | - (void)setLeft:(CGFloat)left right:(CGFloat)right; 93 | - (void)setWidth:(CGFloat)width right:(CGFloat)right; 94 | - (void)setTop:(CGFloat)top bottom:(CGFloat)bottom; 95 | - (void)setHeight:(CGFloat)height bottom:(CGFloat)bottom; 96 | 97 | //animation 98 | 99 | - (void)crossfadeWithDuration:(NSTimeInterval)duration; 100 | - (void)crossfadeWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion; 101 | 102 | @end 103 | 104 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/ViewUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewUtils.m 3 | // 4 | // Version 1.1.2 5 | // 6 | // Created by Nick Lockwood on 19/11/2011. 7 | // Copyright (c) 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/ViewUtils 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | #import "ViewUtils.h" 34 | #import 35 | 36 | 37 | #pragma GCC diagnostic ignored "-Wgnu" 38 | 39 | 40 | @implementation UIView (ViewUtils) 41 | 42 | //nib loading 43 | 44 | + (id)instanceWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil owner:(id)owner 45 | { 46 | //default values 47 | NSString *nibName = nibNameOrNil ?: NSStringFromClass(self); 48 | NSBundle *bundle = bundleOrNil ?: [NSBundle mainBundle]; 49 | 50 | //cache nib to prevent unnecessary filesystem access 51 | static NSCache *nibCache = nil; 52 | if (nibCache == nil) 53 | { 54 | nibCache = [[NSCache alloc] init]; 55 | } 56 | NSString *pathKey = [NSString stringWithFormat:@"%@.%@", bundle.bundleIdentifier, nibName]; 57 | UINib *nib = [nibCache objectForKey:pathKey]; 58 | if (nib == nil) 59 | { 60 | NSString *nibPath = [bundle pathForResource:nibName ofType:@"nib"]; 61 | if (nibPath) nib = [UINib nibWithNibName:nibName bundle:bundle]; 62 | [nibCache setObject:nib ?: [NSNull null] forKey:pathKey]; 63 | } 64 | else if ([nib isKindOfClass:[NSNull class]]) 65 | { 66 | nib = nil; 67 | } 68 | 69 | if (nib) 70 | { 71 | //attempt to load from nib 72 | NSArray *contents = [nib instantiateWithOwner:owner options:nil]; 73 | UIView *view = [contents count]? [contents objectAtIndex:0]: nil; 74 | NSAssert ([view isKindOfClass:self], @"First object in nib '%@' was '%@'. Expected '%@'", nibName, view, self); 75 | return view; 76 | } 77 | 78 | //return empty view 79 | return [[[self class] alloc] init]; 80 | } 81 | 82 | - (void)loadContentsWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil 83 | { 84 | NSString *nibName = nibNameOrNil ?: NSStringFromClass([self class]); 85 | UIView *view = [UIView instanceWithNibName:nibName bundle:bundleOrNil owner:self]; 86 | if (view) 87 | { 88 | if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) 89 | { 90 | //if we have zero size, set size from content 91 | self.size = view.size; 92 | } 93 | else 94 | { 95 | //otherwise set content size to match our size 96 | view.frame = self.contentBounds; 97 | } 98 | [self addSubview:view]; 99 | } 100 | } 101 | 102 | //view searching 103 | 104 | - (UIView *)viewMatchingPredicate:(NSPredicate *)predicate 105 | { 106 | if ([predicate evaluateWithObject:self]) 107 | { 108 | return self; 109 | } 110 | for (UIView *view in self.subviews) 111 | { 112 | UIView *match = [view viewMatchingPredicate:predicate]; 113 | if (match) return match; 114 | } 115 | return nil; 116 | } 117 | 118 | - (UIView *)viewWithTag:(NSInteger)tag ofClass:(Class)viewClass 119 | { 120 | return [self viewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused NSDictionary *bindings) { 121 | return [evaluatedObject tag] == tag && [evaluatedObject isKindOfClass:viewClass]; 122 | }]]; 123 | } 124 | 125 | - (UIView *)viewOfClass:(Class)viewClass 126 | { 127 | return [self viewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused NSDictionary *bindings) { 128 | return [evaluatedObject isKindOfClass:viewClass]; 129 | }]]; 130 | } 131 | 132 | - (NSArray *)viewsMatchingPredicate:(NSPredicate *)predicate 133 | { 134 | NSMutableArray *matches = [NSMutableArray array]; 135 | if ([predicate evaluateWithObject:self]) 136 | { 137 | [matches addObject:self]; 138 | } 139 | for (UIView *view in self.subviews) 140 | { 141 | //check for subviews 142 | //avoid creating unnecessary array 143 | if ([view.subviews count]) 144 | { 145 | [matches addObjectsFromArray:[view viewsMatchingPredicate:predicate]]; 146 | } 147 | else if ([predicate evaluateWithObject:view]) 148 | { 149 | [matches addObject:view]; 150 | } 151 | } 152 | return matches; 153 | } 154 | 155 | - (NSArray *)viewsWithTag:(NSInteger)tag 156 | { 157 | return [self viewsMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused id bindings) { 158 | return [evaluatedObject tag] == tag; 159 | }]]; 160 | } 161 | 162 | - (NSArray *)viewsWithTag:(NSInteger)tag ofClass:(Class)viewClass 163 | { 164 | return [self viewsMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused id bindings) { 165 | return [evaluatedObject tag] == tag && [evaluatedObject isKindOfClass:viewClass]; 166 | }]]; 167 | } 168 | 169 | - (NSArray *)viewsOfClass:(Class)viewClass 170 | { 171 | return [self viewsMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused id bindings) { 172 | return [evaluatedObject isKindOfClass:viewClass]; 173 | }]]; 174 | } 175 | 176 | - (UIView *)firstSuperviewMatchingPredicate:(NSPredicate *)predicate 177 | { 178 | if ([predicate evaluateWithObject:self]) 179 | { 180 | return self; 181 | } 182 | return [self.superview firstSuperviewMatchingPredicate:predicate]; 183 | } 184 | 185 | - (UIView *)firstSuperviewOfClass:(Class)viewClass 186 | { 187 | return [self firstSuperviewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 188 | return [superview isKindOfClass:viewClass]; 189 | }]]; 190 | } 191 | 192 | - (UIView *)firstSuperviewWithTag:(NSInteger)tag 193 | { 194 | return [self firstSuperviewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 195 | return superview.tag == tag; 196 | }]]; 197 | } 198 | 199 | - (UIView *)firstSuperviewWithTag:(NSInteger)tag ofClass:(Class)viewClass 200 | { 201 | return [self firstSuperviewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 202 | return superview.tag == tag && [superview isKindOfClass:viewClass]; 203 | }]]; 204 | } 205 | 206 | - (BOOL)viewOrAnySuperviewMatchesPredicate:(NSPredicate *)predicate 207 | { 208 | if ([predicate evaluateWithObject:self]) 209 | { 210 | return YES; 211 | } 212 | return [self.superview viewOrAnySuperviewMatchesPredicate:predicate]; 213 | } 214 | 215 | - (BOOL)viewOrAnySuperviewIsKindOfClass:(Class)viewClass 216 | { 217 | return [self viewOrAnySuperviewMatchesPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 218 | return [superview isKindOfClass:viewClass]; 219 | }]]; 220 | } 221 | 222 | - (BOOL)isSuperviewOfView:(UIView *)view 223 | { 224 | return [self firstSuperviewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 225 | return superview == view; 226 | }]] != nil; 227 | } 228 | 229 | - (BOOL)isSubviewOfView:(UIView *)view 230 | { 231 | return [view isSuperviewOfView:self]; 232 | } 233 | 234 | //responder chain 235 | 236 | - (UIViewController *)firstViewController 237 | { 238 | id responder = self; 239 | while ((responder = [responder nextResponder])) 240 | { 241 | if ([responder isKindOfClass:[UIViewController class]]) 242 | { 243 | return responder; 244 | } 245 | } 246 | return nil; 247 | } 248 | 249 | - (UIView *)firstResponder 250 | { 251 | return [self viewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused id bindings) { 252 | return [evaluatedObject isFirstResponder]; 253 | }]]; 254 | } 255 | 256 | //frame accessors 257 | 258 | - (CGPoint)origin 259 | { 260 | return self.frame.origin; 261 | } 262 | 263 | - (void)setOrigin:(CGPoint)origin 264 | { 265 | CGRect frame = self.frame; 266 | frame.origin = origin; 267 | self.frame = frame; 268 | } 269 | 270 | - (CGSize)size 271 | { 272 | return self.frame.size; 273 | } 274 | 275 | - (void)setSize:(CGSize)size 276 | { 277 | CGRect frame = self.frame; 278 | frame.size = size; 279 | self.frame = frame; 280 | } 281 | 282 | - (CGFloat)top 283 | { 284 | return self.origin.y; 285 | } 286 | 287 | - (void)setTop:(CGFloat)top 288 | { 289 | CGRect frame = self.frame; 290 | frame.origin.y = top; 291 | self.frame = frame; 292 | } 293 | 294 | - (CGFloat)left 295 | { 296 | return self.origin.x; 297 | } 298 | 299 | - (void)setLeft:(CGFloat)left 300 | { 301 | CGRect frame = self.frame; 302 | frame.origin.x = left; 303 | self.frame = frame; 304 | } 305 | 306 | - (CGFloat)right 307 | { 308 | return self.left + self.width; 309 | } 310 | 311 | - (void)setRight:(CGFloat)right 312 | { 313 | CGRect frame = self.frame; 314 | frame.origin.x = right - frame.size.width; 315 | self.frame = frame; 316 | } 317 | 318 | - (CGFloat)bottom 319 | { 320 | return self.top + self.height; 321 | } 322 | 323 | - (void)setBottom:(CGFloat)bottom 324 | { 325 | CGRect frame = self.frame; 326 | frame.origin.y = bottom - frame.size.height; 327 | self.frame = frame; 328 | } 329 | 330 | - (CGFloat)width 331 | { 332 | return self.size.width; 333 | } 334 | 335 | - (void)setWidth:(CGFloat)width 336 | { 337 | CGRect frame = self.frame; 338 | frame.size.width = width; 339 | self.frame = frame; 340 | } 341 | 342 | - (CGFloat)height 343 | { 344 | return self.size.height; 345 | } 346 | 347 | - (void)setHeight:(CGFloat)height 348 | { 349 | CGRect frame = self.frame; 350 | frame.size.height = height; 351 | self.frame = frame; 352 | } 353 | 354 | - (CGFloat)x 355 | { 356 | return self.center.x; 357 | } 358 | 359 | - (void)setX:(CGFloat)x 360 | { 361 | self.center = CGPointMake(x, self.center.y); 362 | } 363 | 364 | - (CGFloat)y 365 | { 366 | return self.center.y; 367 | } 368 | 369 | - (void)setY:(CGFloat)y 370 | { 371 | self.center = CGPointMake(self.center.x, y); 372 | } 373 | 374 | //bounds accessors 375 | 376 | - (CGSize)boundsSize 377 | { 378 | return self.bounds.size; 379 | } 380 | 381 | - (void)setBoundsSize:(CGSize)size 382 | { 383 | CGRect bounds = self.bounds; 384 | bounds.size = size; 385 | self.bounds = bounds; 386 | } 387 | 388 | - (CGFloat)boundsWidth 389 | { 390 | return self.boundsSize.width; 391 | } 392 | 393 | - (void)setBoundsWidth:(CGFloat)width 394 | { 395 | CGRect bounds = self.bounds; 396 | bounds.size.width = width; 397 | self.bounds = bounds; 398 | } 399 | 400 | - (CGFloat)boundsHeight 401 | { 402 | return self.boundsSize.height; 403 | } 404 | 405 | - (void)setBoundsHeight:(CGFloat)height 406 | { 407 | CGRect bounds = self.bounds; 408 | bounds.size.height = height; 409 | self.bounds = bounds; 410 | } 411 | 412 | //content getters 413 | 414 | - (CGRect)contentBounds 415 | { 416 | return CGRectMake(0.0f, 0.0f, self.boundsWidth, self.boundsHeight); 417 | } 418 | 419 | - (CGPoint)contentCenter 420 | { 421 | return CGPointMake(self.boundsWidth/2.0f, self.boundsHeight/2.0f); 422 | } 423 | 424 | //additional frame setters 425 | 426 | - (void)setLeft:(CGFloat)left right:(CGFloat)right 427 | { 428 | CGRect frame = self.frame; 429 | frame.origin.x = left; 430 | frame.size.width = right - left; 431 | self.frame = frame; 432 | } 433 | 434 | - (void)setWidth:(CGFloat)width right:(CGFloat)right 435 | { 436 | CGRect frame = self.frame; 437 | frame.origin.x = right - width; 438 | frame.size.width = width; 439 | self.frame = frame; 440 | } 441 | 442 | - (void)setTop:(CGFloat)top bottom:(CGFloat)bottom 443 | { 444 | CGRect frame = self.frame; 445 | frame.origin.y = top; 446 | frame.size.height = bottom - top; 447 | self.frame = frame; 448 | } 449 | 450 | - (void)setHeight:(CGFloat)height bottom:(CGFloat)bottom 451 | { 452 | CGRect frame = self.frame; 453 | frame.origin.y = bottom - height; 454 | frame.size.height = height; 455 | self.frame = frame; 456 | } 457 | 458 | //animation 459 | 460 | - (void)crossfadeWithDuration:(NSTimeInterval)duration 461 | { 462 | //jump through a few hoops to avoid QuartzCore framework dependency 463 | CAAnimation *animation = [NSClassFromString(@"CATransition") animation]; 464 | [animation setValue:@"kCATransitionFade" forKey:@"type"]; 465 | animation.duration = duration; 466 | [self.layer addAnimation:animation forKey:nil]; 467 | } 468 | 469 | - (void)crossfadeWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion 470 | { 471 | [self crossfadeWithDuration:duration]; 472 | if (completion) 473 | { 474 | dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)); 475 | dispatch_after(time, dispatch_get_main_queue(), completion); 476 | } 477 | } 478 | 479 | @end 480 | -------------------------------------------------------------------------------- /LLSimpleCameraExample/camera-flash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omergul/LLSimpleCamera/01889954c1f7c9fa153f7c0f08521698128d528d/LLSimpleCameraExample/camera-flash.png -------------------------------------------------------------------------------- /LLSimpleCameraExample/camera-flash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omergul/LLSimpleCamera/01889954c1f7c9fa153f7c0f08521698128d528d/LLSimpleCameraExample/camera-flash@2x.png -------------------------------------------------------------------------------- /LLSimpleCameraExample/camera-switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omergul/LLSimpleCamera/01889954c1f7c9fa153f7c0f08521698128d528d/LLSimpleCameraExample/camera-switch.png -------------------------------------------------------------------------------- /LLSimpleCameraExample/camera-switch@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omergul/LLSimpleCamera/01889954c1f7c9fa153f7c0f08521698128d528d/LLSimpleCameraExample/camera-switch@2x.png -------------------------------------------------------------------------------- /LLSimpleCameraExample/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omergul/LLSimpleCamera/01889954c1f7c9fa153f7c0f08521698128d528d/LLSimpleCameraExample/cancel.png -------------------------------------------------------------------------------- /LLSimpleCameraExample/cancel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omergul/LLSimpleCamera/01889954c1f7c9fa153f7c0f08521698128d528d/LLSimpleCameraExample/cancel@2x.png -------------------------------------------------------------------------------- /LLSimpleCameraExample/cancel@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omergul/LLSimpleCamera/01889954c1f7c9fa153f7c0f08521698128d528d/LLSimpleCameraExample/cancel@3x.png -------------------------------------------------------------------------------- /LLSimpleCameraExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LLSimpleCameraExample 4 | // 5 | // Created by Ömer Faruk Gül on 29/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LLSimpleCameraExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.omerfarukgul.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LLSimpleCameraExampleTests/LLSimpleCameraExampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LLSimpleCameraExampleTests.m 3 | // LLSimpleCameraExampleTests 4 | // 5 | // Created by Ömer Faruk Gül on 29/10/14. 6 | // Copyright (c) 2014 Ömer Faruk Gül. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface LLSimpleCameraExampleTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation LLSimpleCameraExampleTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLSimpleCamera: A simple customizable camera - video recorder control 2 | 3 | ![Screenshot](https://raw.githubusercontent.com/omergul123/LLSimpleCamera/master/screenshot.png) 4 | 5 | LLSimpleCamera is a library for creating a customized camera - video recorder screens similar to snapchat's. You don't have to present the camera in a new view controller. 6 | 7 | You can also use my [LLVideoEditor][1] library to easily edit recorded videos. 8 | 9 | ###LLSimpleCamera:### 10 | * lets you easily capture photos and record videos 11 | * handles the position and flash of the camera 12 | * hides the nitty gritty details from the developer 13 | * doesn't have to be presented in a new modal view controller, simply can be embedded inside any of your VCs. (like Snapchat) 14 | 15 | 16 | ###Version 5.0 notes:### 17 | - Better recording API 18 | - Improved reliability 19 | 20 | ###Version 4.2 notes:### 21 | New features: 22 | - zoom feature 23 | - white balance configuration 24 | - attaching to view controller improved 25 | 26 | ###Version 4.1 notes:### 27 | Merged some PRs: 28 | - camera mirroring option 29 | - implementation of **- (instancetype)initWithCoder:(NSCoder *)aDecoder** 30 | 31 | ###Version 4.0 notes:### 32 | Thanks to the open source community, recently I have merged about 10 PR's to make this library much better and reliable. Also I did some cleanups which contains some breaking changes (sorry for that). Therefore I'm incrementing the major version. 33 | 34 | ## Install 35 | 36 | pod 'LLSimpleCamera', '~> 4.1' 37 | 38 | ## Example usage 39 | 40 | Initialize the LLSimpleCamera 41 | ```objective-c 42 | CGRect screenRect = [[UIScreen mainScreen] bounds]; 43 | 44 | // create camera with standard settings 45 | self.camera = [[LLSimpleCamera alloc] init]; 46 | 47 | // camera with video recording capability 48 | self.camera = [[LLSimpleCamera alloc] initWithVideoEnabled:YES]; 49 | 50 | // camera with precise quality, position and video parameters. 51 | self.camera = [[LLSimpleCamera alloc] initWithQuality:AVCaptureSessionPresetHigh 52 | position:LLCameraPositionRear 53 | videoEnabled:YES]; 54 | // attach to the view 55 | [self.camera attachToViewController:self withFrame:CGRectMake(0, 0, screenRect.size.width, screenRect.size.height)]; 56 | 57 | ``` 58 | 59 | To capture a photo: 60 | ```objective-c 61 | // capture 62 | [self.camera capture:^(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error) { 63 | if(!error) { 64 | // we should stop the camera, since we don't need it anymore. We will open a new vc. 65 | // this very important, otherwise you may experience memory crashes 66 | [camera stop]; 67 | 68 | // show the image 69 | ImageViewController *imageVC = [[ImageViewController alloc] initWithImage:image]; 70 | [self presentViewController:imageVC animated:NO completion:nil]; 71 | } 72 | }]; 73 | ``` 74 | 75 | To start recording a video: 76 | ```objective-c 77 | // start recording 78 | NSURL *outputURL = [[[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"test1"] URLByAppendingPathExtension:@"mov"]; 79 | [self.camera startRecordingWithOutputUrl:outputURL didRecord:^(LLSimpleCamera *camera, NSURL *outputFileUrl, NSError *error) { 80 | VideoViewController *vc = [[VideoViewController alloc] initWithVideoUrl:outputFileUrl]; 81 | [self.navigationController pushViewController:vc animated:YES]; 82 | }]; 83 | ``` 84 | 85 | To stop recording the video: 86 | ```objective-c 87 | [self.camera stopRecording]; 88 | ``` 89 | 90 | Changing the focus layer and animation: 91 | ```objective-c 92 | - (void)alterFocusBox:(CALayer *)layer animation:(CAAnimation *)animation; 93 | ``` 94 | 95 | ## Adding the camera controls 96 | 97 | You have to add your own camera controls (flash, camera switch etc). Simply add the controls to the view where LLSimpleCamera is attached to. You can see a full camera example in the example project. Download and try it on your device. 98 | 99 | ## Stopping and restarting the camera 100 | 101 | You should never forget to stop the camera either after the capture block is triggered, or inside somewhere **-viewWillDisappear** of the parent controller to make sure that the app doesn't use the camera when it is not needed. You can call **-start()** to reuse the camera. So it may be good idea to to place **-start()** inside **-viewWillAppear** or in another relevant method. 102 | 103 | ## Contact 104 | 105 | Ömer Faruk Gül 106 | 107 | [Personal Site][2] 108 | 109 | omer@omerfarukgul.com 110 | 111 | ## Version History 112 | 113 | #### Version 3.0.0 114 | - added video recording capability 115 | - class is heavily refactored 116 | 117 | #### Version 2.2.0 118 | - camera permissions are supported, if the permission is not given by the user, onError will be triggered. 119 | - camera flash methods are altered. Now you have to call **- (BOOL)updateFlashMode:(CameraFlash)cameraFlash;** 120 | - cameraFlash and cameraPosition property names are simplified to: **flash** and **position**. 121 | - added support for device orientation in case your vc orientation is locked but you want to use the device orientation no matter what. 122 | 123 | #### Version 2.1.1 124 | - freezing the screen just after the photo is taken for better user experience. 125 | 126 | #### Version 2.1.0 127 | - added an extra parameter exactSeenImage:(BOOL)exactSeenImage to -capture method to easily get the exact seen image on the screen instead of the raw uncropped image. The default value is NO. 128 | - fixed an orientation bug inside capture method. 129 | 130 | #### Version 2.0.0 131 | Some significant changes have been made at both internal structure and api. 132 | - added tap to focus feature (it is fully customizable, if you don't like the default layer and animation) 133 | - removed delegates and added blocks 134 | - interface is significantly improved 135 | 136 | #### Version 1.1.1 137 | - fixed a potential crash scenario if -stop() is called multiple times 138 | 139 | #### Version 1.1.0 140 | - fixed a problem that sometimes caused a crash after capturing a photo. 141 | - improved code structure, didChangeDevice delegate is now also triggered for the first default device. 142 | 143 | [1]: http://github.com/omergul123/LLVideoEditor 144 | [2]: http://omerfarukgul.com 145 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omergul/LLSimpleCamera/01889954c1f7c9fa153f7c0f08521698128d528d/screenshot.png --------------------------------------------------------------------------------