├── ScanBanner.png ├── Scanner App ├── Scanner App │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon29x29@2x.png │ │ │ ├── AppIcon40x40@2x.png │ │ │ ├── AppIcon60x60@2x.png │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── AppDelegate.h │ ├── main.m │ ├── Scanner App-Prefix.pch │ ├── ViewController.h │ ├── Scanner App-Info.plist │ ├── AppDelegate.m │ ├── ViewController.m │ └── Base.lproj │ │ └── Main.storyboard └── Scanner App.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── Spencers.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ ├── xcuserdata │ └── Spencers.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── Scanner App.xcscheme │ └── project.pbxproj ├── RMScannerView ├── RMOutlineBox.h ├── RMOutlineBox.m ├── RMScannerView.h └── RMScannerView.m ├── RMScannerView.podspec ├── LICENSE.md └── README.md /ScanBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nenosinc/RMScannerView/HEAD/ScanBanner.png -------------------------------------------------------------------------------- /Scanner App/Scanner App/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Scanner App/Scanner App/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nenosinc/RMScannerView/HEAD/Scanner App/Scanner App/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png -------------------------------------------------------------------------------- /Scanner App/Scanner App/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nenosinc/RMScannerView/HEAD/Scanner App/Scanner App/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png -------------------------------------------------------------------------------- /Scanner App/Scanner App/Images.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nenosinc/RMScannerView/HEAD/Scanner App/Scanner App/Images.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png -------------------------------------------------------------------------------- /Scanner App/Scanner App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Scanner App/Scanner App.xcodeproj/project.xcworkspace/xcuserdata/Spencers.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nenosinc/RMScannerView/HEAD/Scanner App/Scanner App.xcodeproj/project.xcworkspace/xcuserdata/Spencers.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Scanner App/Scanner App/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Scanner App 4 | // 5 | // Created by iRare Media on 12/4/13. 6 | // Copyright (c) 2013 iRare Media. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Scanner App/Scanner App/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Scanner App 4 | // 5 | // Created by iRare Media on 12/4/13. 6 | // Copyright (c) 2013 iRare Media. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Scanner App/Scanner App/Scanner App-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Scanner App/Scanner App/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Scanner App/Scanner App/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "AppIcon29x29@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "40x40", 11 | "idiom" : "iphone", 12 | "filename" : "AppIcon40x40@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "60x60", 17 | "idiom" : "iphone", 18 | "filename" : "AppIcon60x60@2x.png", 19 | "scale" : "2x" 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | } 26 | } -------------------------------------------------------------------------------- /Scanner App/Scanner App/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Scanner App 4 | // 5 | // Created by iRare Media on 12/4/13. 6 | // Copyright (c) 2013 iRare Media. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "RMScannerView.h" 11 | 12 | @interface ViewController : UIViewController 13 | 14 | @property (strong, nonatomic) IBOutlet RMScannerView *scannerView; 15 | @property (weak, nonatomic) IBOutlet UILabel *statusText; 16 | 17 | @property (weak, nonatomic) IBOutlet UIBarButtonItem *sessionToggleButton; 18 | - (IBAction)startNewScannerSession:(id)sender; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Scanner App/Scanner App.xcodeproj/xcuserdata/Spencers.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Scanner App.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 99A314DF1850253F000917FA 16 | 17 | primary 18 | 19 | 20 | 99A3150018502540000917FA 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /RMScannerView/RMOutlineBox.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMOutlineBox.h 3 | // Scanner App 4 | // 5 | // Created by iRare Media on 1/22/14. 6 | // Copyright (c) 2014 iRare Media. All rights reserved. 7 | // 8 | 9 | #if __has_feature(objc_modules) 10 | // We recommend enabling Objective-C Modules in your project Build Settings for numerous benefits over regular #imports. Read more from the Modules documentation: http://clang.llvm.org/docs/Modules.html 11 | @import Foundation; 12 | @import UIKit; 13 | #else 14 | #import 15 | #import 16 | #endif 17 | 18 | /// Draws the outline of the scanned barcode 19 | @interface RMOutlineBox : UIView 20 | 21 | /// The corners of the scanned barcode 22 | @property (nonatomic, strong) NSArray *corners; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /RMScannerView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'RMScannerView' 3 | s.version = '1.3' 4 | s.platform = :ios, '7.0' 5 | s.license = 'MIT' 6 | s.summary = 'Simple barcode scanner UIView subclass for iOS apps.' 7 | s.homepage = 'https://github.com/iRareMedia/RMScannerView' 8 | s.author = { 'Sam Spencer' => 'contact@iraremedia.com' } 9 | s.source = { :git => 'https://github.com/iRareMedia/RMScannerView.git', :tag => s.version.to_s } 10 | 11 | s.description = 'Simple barcode scanner UIView subclass for iOS apps. ' \ 12 | 'Quickly and efficiently scans a large variety of barcodes ' \ 13 | 'using the iOS device\'s built in camera. ' 14 | 15 | s.frameworks = ['AVFoundation', 'CoreGraphics'] 16 | s.source_files = 'RMScannerView/*.{h,m}' 17 | s.preserve_paths = 'Scanner App' 18 | s.requires_arc = true 19 | end 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 iRare Media 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Scanner App/Scanner App/Scanner App-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.iRare-Media.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.3 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.3 25 | LSRequiresIPhoneOS 26 | 27 | NSCameraUsageDescription 28 | Enable scanner 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /RMScannerView/RMOutlineBox.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMOutlineBox.m 3 | // Scanner App 4 | // 5 | // Created by iRare Media on 1/22/14. 6 | // Copyright (c) 2014 iRare Media. All rights reserved. 7 | // 8 | 9 | #import "RMOutlineBox.h" 10 | 11 | @interface RMOutlineBox () 12 | @property (nonatomic, strong) CAShapeLayer *outline; 13 | @end 14 | 15 | @implementation RMOutlineBox 16 | 17 | - (id)initWithFrame:(CGRect)frame { 18 | self = [super initWithFrame:frame]; 19 | if (self) { 20 | // Initialization code 21 | _outline = [CAShapeLayer new]; 22 | _outline.strokeColor = [[[UIColor redColor] colorWithAlphaComponent:0.8] CGColor]; 23 | _outline.lineWidth = 2.5; 24 | _outline.fillColor = [[UIColor clearColor] CGColor]; 25 | [self.layer addSublayer:_outline]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)setCorners:(NSArray *)corners { 31 | if (corners != _corners) { 32 | _corners = corners; 33 | _outline.path = [[self createOutlineFromCorners:corners] CGPath]; 34 | } 35 | } 36 | 37 | - (UIBezierPath *)createOutlineFromCorners:(NSArray *)points { 38 | // Create a new bezier path 39 | UIBezierPath *path = [UIBezierPath new]; 40 | 41 | // AVFoundation provides points in an array, ordered counterclockwise 42 | [path moveToPoint:[[points firstObject] CGPointValue]]; 43 | 44 | // Draw lines around the corners 45 | for (NSUInteger i = 1; i < [points count]; i++) { 46 | [path addLineToPoint:[points[i] CGPointValue]]; 47 | } 48 | 49 | // Close up the line to the first corner - complete the path 50 | [path addLineToPoint:[[points firstObject] CGPointValue]]; 51 | 52 | return path; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Scanner App/Scanner App/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Scanner App 4 | // 5 | // Created by iRare Media on 12/4/13. 6 | // Copyright (c) 2013 iRare Media. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 14 | // Override point for customization after application launch. 15 | return YES; 16 | } 17 | 18 | - (void)applicationWillResignActive:(UIApplication *)application { 19 | // 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. 20 | // 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. 21 | } 22 | 23 | - (void)applicationDidEnterBackground:(UIApplication *)application { 24 | // 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. 25 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 26 | } 27 | 28 | - (void)applicationWillEnterForeground:(UIApplication *)application { 29 | // 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. 30 | } 31 | 32 | - (void)applicationDidBecomeActive:(UIApplication *)application { 33 | // 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. 34 | } 35 | 36 | - (void)applicationWillTerminate:(UIApplication *)application { 37 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Scanner App/Scanner App/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Scanner App 4 | // 5 | // Created by iRare Media on 12/4/13. 6 | // Copyright (c) 2013 iRare Media. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | @end 13 | 14 | @implementation ViewController 15 | @synthesize scannerView, statusText; 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view, typically from a nib. 20 | 21 | // Set verbose logging to YES so we can see exactly what's going on 22 | [scannerView setVerboseLogging:YES]; 23 | 24 | // Set animations to YES for some nice effects 25 | [scannerView setAnimateScanner:YES]; 26 | 27 | // Set code outline to YES for a box around the scanned code 28 | [scannerView setDisplayCodeOutline:YES]; 29 | 30 | // Set concrete video orientation enabled 31 | // [scannerView setEnableAutorotation:NO]; 32 | // [scannerView setDefaultCaptureVideoOrientation:AVCaptureVideoOrientationPortrait]; 33 | 34 | // Start the capture session when the view loads - this will also start a scan session 35 | [scannerView startCaptureSession]; 36 | 37 | // Set the title of the toggle button 38 | self.sessionToggleButton.title = @"Stop"; 39 | } 40 | 41 | - (void)didReceiveMemoryWarning { 42 | [super didReceiveMemoryWarning]; 43 | // Dispose of any resources that can be recreated. 44 | } 45 | 46 | - (IBAction)startNewScannerSession:(id)sender { 47 | if ([scannerView isScanSessionInProgress]) { 48 | [scannerView stopScanSession]; 49 | self.sessionToggleButton.title = @"Start"; 50 | } else { 51 | [scannerView startScanSession]; 52 | self.sessionToggleButton.title = @"Stop"; 53 | } 54 | } 55 | 56 | - (void)didScanCode:(NSString *)scannedCode onCodeType:(NSString *)codeType { 57 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:@"Scanned %@", [scannerView humanReadableCodeTypeForCode:codeType]] message:scannedCode delegate:self cancelButtonTitle:@"Okay" otherButtonTitles:@"New Session", nil]; 58 | [alert show]; 59 | } 60 | 61 | - (void)errorGeneratingCaptureSession:(NSError *)error { 62 | [scannerView stopCaptureSession]; 63 | 64 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Unsupported Device" message:@"This device does not have a camera. Run this app on an iOS device that has a camera." delegate:nil cancelButtonTitle:nil otherButtonTitles:nil]; 65 | [alert show]; 66 | 67 | statusText.text = @"Unsupported Device"; 68 | self.sessionToggleButton.title = @"Error"; 69 | } 70 | 71 | - (void)errorAcquiringDeviceHardwareLock:(NSError *)error { 72 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Focus Unavailable" message:@"Tap to focus is currently unavailable. Try again in a little while." delegate:nil cancelButtonTitle:@"Okay" otherButtonTitles:nil]; 73 | [alert show]; 74 | } 75 | 76 | - (BOOL)shouldEndSessionAfterFirstSuccessfulScan { 77 | // Return YES to only scan one barcode, and then finish - return NO to continually scan. 78 | // If you plan to test the return NO functionality, it is recommended that you remove the alert view from the "didScanCode:" delegate method implementation 79 | // The Display Code Outline only works if this method returns NO 80 | return YES; 81 | } 82 | 83 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 84 | if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:@"New Session"]) { 85 | [scannerView startScanSession]; 86 | self.sessionToggleButton.title = @"Stop"; 87 | } else if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:@"Okay"]) { 88 | self.sessionToggleButton.title = @"Start"; 89 | } 90 | } 91 | 92 | - (UIBarPosition)positionForBar:(id )bar { 93 | return UIBarPositionTopAttached; 94 | } 95 | @end 96 | -------------------------------------------------------------------------------- /Scanner App/Scanner App.xcodeproj/xcuserdata/Spencers.xcuserdatad/xcschemes/Scanner App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Scanner App/Scanner App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /RMScannerView/RMScannerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMScannerView.h 3 | // RMScannerView 4 | // 5 | // Created by iRare Media on 12/3/13. 6 | // Copyright (c) 2014 iRare Media. All rights reserved. 7 | // 8 | 9 | 10 | #if __has_feature(objc_modules) 11 | // We recommend enabling Objective-C Modules in your project Build Settings for numerous benefits over regular #imports. Read more from the Modules documentation: http://clang.llvm.org/docs/Modules.html 12 | @import Foundation; 13 | @import UIKit; 14 | @import AVFoundation; 15 | #else 16 | #import 17 | #import 18 | #import 19 | #endif 20 | 21 | #if !__has_feature(objc_arc) 22 | // Add the -fobjc-arc flag to enable ARC for only these files, as described in the ARC documentation: http://clang.llvm.org/docs/AutomaticReferenceCounting.html 23 | #error RMScannerView is built with Objective-C ARC. You must enable ARC for these files. 24 | #endif 25 | 26 | #ifndef __IPHONE_7_0 27 | #error RMScannerView is built with features only available is iOS SDK 7.0 and later. 28 | #endif 29 | 30 | 31 | #import "RMOutlineBox.h" 32 | 33 | 34 | @class RMScannerView; 35 | @protocol RMScannerViewDelegate; 36 | 37 | /** A UIView subclass for scanning and reading barcodes. 38 | Quickly and efficiently scans a large variety of barcodes using the device's built in hardware */ 39 | NS_CLASS_AVAILABLE_IOS(7_0) @interface RMScannerView : UIView 40 | 41 | @property (strong) AVCaptureSession *captureSession; 42 | 43 | /// Verbose logging prints extra messages to the log which explains what's going on 44 | @property BOOL verboseLogging; 45 | 46 | /// Display scanner animations - red scan line moving up and down and stops when a barcode is found 47 | @property BOOL animateScanner; 48 | 49 | /// Display code outline - red box appears around barcode when it is detected - disappears after inactivity. Only appears if the delegate method, \p shouldEndSessionAfterFirstSuccessfulScan returns NO. 50 | @property BOOL displayCodeOutline; 51 | 52 | /// Enable autorotation - video orientation changes when device orientation did change. If enableAutorotation is NO, video orientation is defaultCaptureVideoOrientation. By default enableAutorotation is YES 53 | @property BOOL enableAutorotation; 54 | 55 | /// Default captureVideo orientation - capture video orientation if autorotation disabled. By default defaultCaptureVideoOrientation is AVCaptureVideoOrientationPortrait 56 | @property AVCaptureVideoOrientation defaultCaptureVideoOrientation; 57 | 58 | /// Scanner line color used for scanner animations 59 | @property UIColor *scannerLineColor UI_APPEARANCE_SELECTOR; 60 | 61 | /// The RMScannerView delegate object used to set the delegate. The delegate reports scan data, errors, and requests information from the delegate. 62 | @property (nonatomic, weak) IBOutlet id delegate; 63 | 64 | /** Checks if a scan session is in progress 65 | @return YES if a scan session is currently in progress. NO if either a scan session or a capture session are not in progress. */ 66 | - (BOOL)isScanSessionInProgress; 67 | 68 | /** Checks if a capture session is in progress 69 | @return YES if a capture session is currently in progress. NO a capture session is not in progress. May return YES even if a scan session is \b not in progress. */ 70 | - (BOOL)isCaptureSessionInProgress; 71 | 72 | /** Starts the current barcode scanner capture session 73 | @discussion This method should be called when the encapsulating UIViewController is presented or is loaded, or at any appropriate time. A session will not automatically start when the RMScannerView is loaded (ex. by an interface file). Calling this method begins the AVCaptureSession and starts the collection of camera data - including scan data. */ 74 | - (void)startCaptureSession; 75 | 76 | /** Starts a new scanning session and keeps the same capture session (or creates a new one if none exist) 77 | @discussion This method can be called to start a new scanning session after one has been stopped (ex. automatically after a scan). This will start a new stream of scan data. */ 78 | - (void)startScanSession; 79 | 80 | /** Stops the current barcode scan. This only prevents the scan data from being read. It will not stop any video feed or halt any animations. 81 | @discussion This method should be called when a scan has completed (if continuous scans are not enabled) but the scanner is still visible on screen. */ 82 | - (void)stopScanSession; 83 | 84 | /** Stops the current barcode scanner capture session. This causes the video feed to freeze, animations to halt, and prevents the scan data from being read. 85 | @discussion This method should be called when the encapsulating UIViewController is dismissed, unloaded, or deallocated. Calling this method stops the AVCaptureSession and prevents the collection of any further camera or hardware data - including scan data. It will also remove any animations on the view. */ 86 | - (void)stopCaptureSession; 87 | 88 | /** Converts the \p codeType passed in the \p didScanCode:onCodeType: delegate method to a human readable barcode type name 89 | @param codeType The AVMetadataObjectType string passed in the \p didScanCode:onCodeType: delegate method, or any AVMetadataObjectType barcode string. 90 | @return A human-friendly barcode type name. May return \p nil if the barcode type is not recognized */ 91 | - (NSString *)humanReadableCodeTypeForCode:(NSString *)codeType; 92 | 93 | /** Set the flash mode for the current scan session. Ending a scan session turns off the flash automatically. 94 | @param flashMode The AVCaptureFlashMode which specifies the flash mode, ON, OFF, or AUTO. */ 95 | - (void)setDeviceFlash:(AVCaptureFlashMode)flashMode; 96 | 97 | /** Set the torch mode for the current scan session. Ending a scan session turns off the torch automatically. 98 | @param torchMode The AVCaptureTorchMode which specifies the torch mode, ON, OFF, or AUTO. */ 99 | - (void)setDeviceTorch:(AVCaptureTorchMode)torchMode; 100 | 101 | @end 102 | 103 | @class RMScannerView; 104 | 105 | /** The delegate object for the scanner reports all errors and scans, it also retieves data from the delegate about how the scanner should behave. */ 106 | @protocol RMScannerViewDelegate 107 | 108 | @required 109 | 110 | /** Sent to the delegate when a barcode is successfully scanned 111 | @param scannedCode The readable scanned barcode string 112 | @param codeType The type of barcode which was scanned */ 113 | - (void)didScanCode:(NSString *)scannedCode onCodeType:(NSString *)codeType; 114 | 115 | /** Sent to the delegate when the scanner fails to properly setup the scan session. This usually occurs because it was started on a device without a camera. A possible solution would be to present the user with a prompt to manually enter the barcode text. 116 | @param error The relevant error object containg the error code, solutions, and reasons. */ 117 | - (void)errorGeneratingCaptureSession:(NSError *)error; 118 | 119 | @optional 120 | 121 | /** Sent to the delegate when the user taps to focus and the scanner fails to attain a hardware lock on the users device. A hardware lock must be attained in order to focus the device camera at the point where the user tapped. 122 | @param error The relevant error object containg the error code, solutions, and reasons. */ 123 | - (void)errorAcquiringDeviceHardwareLock:(NSError *)error; 124 | 125 | /** Sent to the delegate when the scanner needs to know if it should stop scanning, or continously scan until the \p stopCaptureSession method is called. 126 | @discussion If YES is returned by this method, the scan session is ended, but the capture session will continue. You must call \p stopCaptureSession manually. 127 | @return YES if the scanner should stop looking for barcodes and reporting them after the first successful scan. NO if the scanner should continuously scan for barcodes - this may result in a constant stream of data from the \p didScanCode:onCodeType method. */ 128 | - (BOOL)shouldEndSessionAfterFirstSuccessfulScan; 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Simple barcode scanner UIView subclass for iOS apps. Quickly and efficiently scans a large variety of barcodes using the iOS device's built in camera. 4 | 5 | RMScannerView is a UIView subclass for scanning barcodes in iOS applications. RMScannerView uses advanced barcode scanning built specifically for iOS. Scan both 2D and 1D barcodes such as PDF417, QR, Aztec, EAN, UPC, Code 128, etc. Get a barcode scanner up and running in your iOS app in only a few minutes. 6 | 7 | If you like the project, please [star it](https://github.com/iRareMedia/RMScannerView) on GitHub! Watch the project on GitHub for updates. If you use RMScannerView in your app, send an email to contact@iraremedia.com or let us know on Twitter @iRareMedia. 8 | 9 | # Project Features 10 | RMScannerView is a great way to integrate barcode scanning in your iOS app. Below are a few key project features and highlights. 11 | * Scan Aztec, Code 128, Code 39, Code 39 Mod 43, Code 93, EAN13, EAN8, PDF417, QR, and UPCE codes. 12 | * Use the iOS device's native hardware / camera and corresponding AVFoundation classes 13 | * Supports tap-to-focus, auto focus, and various other AVCaptureSession features 14 | * Setup only takes a few minutes and can be done almost entirely in interface files 15 | * Access in-depth documentation, code comments, and verbose logging 16 | * Delegate methods, properties, and methods give you complete control over your scan 17 | * Scan animations and scan outlines create a beautiful and dynamic user interface 18 | * iOS Sample-app demonstrates how to quickly and easily setup a RMScannerView 19 | * Frequent updates to the project based on user issues and requests 20 | * Easily contribute to the project 21 | 22 | # Project Information 23 | Learn more about the project requirements, licensing, and contributions. Check the *Releases* section of GitHub for more specific version information. 24 | 25 | ## Requirements 26 | Requires Xcode 5.0.1 for use in any iOS Project. Requires a minimum of iOS 7.0 as the deployment target. Works with and is optimized for ARC and 64-bit Architecture (arm64). 27 | * Supported build target - iOS 7.0 (Xcode 5.0.1, Apple LLVM compiler 5.0) 28 | * Earliest supported deployment target - iOS 7.0 29 | * Earliest compatible deployment target - iOS 7.0 30 | 31 | NOTE: 'Supported' means that the library has been tested with this version. 'Compatible' means that the library should work on this OS version (i.e. it doesn't rely on any unavailable SDK features) but is no longer being tested for compatibility and may require tweaking or bug fixes to run correctly. 32 | 33 | ## License 34 | You are free to make changes and use this in either personal or commercial projects. Attribution is not required, but it appreciated. A little *Thanks!* (or something to that affect) would be much appreciated. If you use RMScannerView in your app, send an email to contact@iraremedia.com or let us know on Twitter @iRareMedia. See the [full RMScannerView license here](https://github.com/iRareMedia/RMScannerView/blob/master/LICENSE.md). 35 | 36 | ## Contributions 37 | Any contribution is more than welcome! You can contribute through pull requests and issues on GitHub. Learn more [about contributing to the project here](https://github.com/iRareMedia/RMScannerView/blob/master/CONTRIBUTING.md). 38 | 39 | ## Sample App 40 | The iOS Sample App included with this project demonstrates how to setup and use many of the features in RMScannerView. 41 | 42 | # Documentation 43 | All methods, properties, types, and delegate methods available on the RMScannerView class are documented below. If you're using [Xcode 5](https://developer.apple.com/technologies/tools/whats-new.html) with RMScannerView, documentation is available directly within Xcode (just Option-Click any method for Quick Help). 44 | 45 | ## Setup 46 | Adding RMScannerView to your project is easy. Follow these steps below to get everything up and running. 47 | 48 | 1. Add the `RMScannerView.h`, `RMScannerView.m`, `RMOutlineBox.h`, and `RMOutlineBox.m` files into your project 49 | 2. Import where necessary, `#import "RMScannerView.h"` 50 | 3. Setup the RMScannerView when your UIViewController or view loads: 51 | 52 | [scannerView setVerboseLogging:YES]; // Set verbose logging to YES so we can see exactly what's going on 53 | [scannerView startCaptureSession]; // Start the capture session when the view loads - this will also start a scan session 54 | 55 | 4. Add a UIView to the corresponding / desired View Controller and set the view's custom class to `RMScannerView` 56 | 5. Subscribe to the `RMScannerView` delegate either through the interface (using the outlets inspector) or through code by subscribing to the `RMScannerViewDelegate` and then setting it. 57 | 58 | ## Sessions 59 | RMScannerView manages barcode scanning in *sessions* which can be started or stopped at anytime. There are various session levels, each having a slightly different effect which is suitable for a different period. The **capture session** is the encompassing session which includes the stream of camera data, scan data, and any animations on the view. The **scan session** is part of the capture session. It includes only the scan data. A scan session can be stopped separate of the capture session. 60 | 61 | ## Methods 62 | There are many methods available on RMScannerView. The most important / highlight methods are documented below. All other methods are documented in the header file and with in-code comments. You should not attempt to use methods which are not listed in the header file. 63 | 64 | ### Starting a Capture Session 65 | Starts the current barcode scanner capture session. This method should be called when the encapsulating UIViewController is presented or is loaded, or at any appropriate time. A session will not automatically start when the RMScannerView is loaded (ex. by an interface file). Calling this method begins the AVCaptureSession and starts the collection of camera data - including scan data. 66 | 67 | [scannerView startCaptureSession]; 68 | 69 | ### Starting a Scan Session 70 | Starts a new scanning session and keeps the same capture session (or creates a new one if none exist). This method can be called to start a new scanning session after one has been stopped (ex. automatically after a scan). This will start a new stream of scan data. 71 | 72 | [scannerView startScanSession]; 73 | 74 | You do not need to start both a scan and capture session at the same time. They are mutually inclusive. Starting a scan session will start a capture session if one does not exist or is stopped. Starting a capture session will also start a scan session. **However**, capture sessions differ from scan sessions in that they are more of an initializer - they do extra setup work and checks that may not be necessary if one is already started. 75 | 76 | ### Stopping a Scan Session 77 | Stops the current barcode scan. This only prevents the scan data collection. It will not stop any video feed or halt any animations. This method may be called when a scan has completed (if continuous scans are not enabled) but the scanner is still visible on screen. 78 | 79 | [scannerView stopScanSession]; 80 | 81 | ### Ending a Capture Session 82 | Stops the current barcode scanner capture session. This causes the video feed to freeze, animations to halt, and prevents the scan data collection. This method should be called when the encapsulating UIViewController is dismissed, unloaded, or deallocated. Calling this method stops the AVCaptureSession and prevents the collection of any further camera or hardware data - including scan data. It will also remove any animations on the view. 83 | 84 | [scannerView stopCaptureSession]; 85 | 86 | ### Checking for Sessions 87 | Check if a scan session is in progress. Returns YES if a scan session is currently in progress. NO if either a scan session or a capture session are not in progress. 88 | 89 | BOOL isScanning = [scannerView isScanSessionInProgress]; 90 | 91 | Check if a capture session is in progress. Return YES if a capture session is currently in progress. NO a capture session is not in progress. May return YES even if a scan session is **not** in progress. 92 | 93 | BOOL isCapturing = [scannerView isCaptureSessionInProgress]; 94 | 95 | ### Parsing Scan Data 96 | If needed, you can display the scanned barcode format to your user with this method. It converts the `codeType` object passed in the `didScanCode:onCodeType:` delegate method to a human readable barcode type name. The `codeType` is an AVMetadataObjectType string passed in the `didScanCode:onCodeType:` delegate method, or any AVMetadataObjectType barcode string. Returns a human-friendly barcode type name. May return `nil` if the barcode type is not recognized. 97 | 98 | NSString *barcodeType = [scannerView humanReadableCodeTypeForCode:codeType]; 99 | 100 | ### Camera Flash 101 | Turn the camera flash to ON, OFF, or AUTO for the current scan session. 102 | 103 | [scannerView setDeviceFlash:AVCaptureFlashMode]; 104 | 105 | ## Delegates 106 | The most important part of the RMScannerView is it's delegate. Information is sent to the delegate about scanned barcodes. Requests are also sent to the delegate to gather preferences and settings. Error messages may also be sent to the delegate. 107 | 108 | ### Gathering Scan Data 109 | When a barcode is recognized and scanned, this delegate method is called. You can use this delegate method to retrieve scan information such as the barcode data and barcode type. This method may be continuously called if the `shouldEndSessionAfterFirstSuccessfulScan` delegate method returns NO. 110 | 111 | - (void)didScanCode:(NSString *)scannedCode onCodeType:(NSString *)codeType; 112 | 113 | ### Registering for Errors 114 | There are two types of errors that may occur when trying to setup or scan. 115 | 116 | The first is a capture session error - a required delegate method. It is sent to the delegate when the scanner fails to properly setup the scan session. This usually occurs because it was started on a device without a camera. A possible solution would be to present the user with a prompt to manually enter the barcode text. 117 | 118 | - (void)errorGeneratingCaptureSession:(NSError *)error; 119 | 120 | The second is a device hardware lock error - an optional delegate method. It is sent to the delegate when the scanner cannot acquire a hardware lock in order to change configurations. This usually occurs when the user taps-to-focus. This may occur because another running application or process has a lock on the hardware configuration which prevents the scanner from focusing the camera. 121 | 122 | - (void)errorAcquiringDeviceHardwareLock:(NSError *)error; 123 | 124 | ### Session Settings 125 | After a barcode is scanned, the scanner must decide to continuously send data to the delegate or to stop sending information after the first successful scan. Use this delegate to specify whether data should continue to be sent or just sent once. Continuous data means that `didScanCode:onCodeType:` delegate is called repeatedly until the barcode disappears or is no longer readable. 126 | 127 | Return YES if the scanner should stop looking for barcodes and reporting them after the first successful scan. NO if the scanner should continuously scan for barcodes. Leaving this delegate method unimplemented will result in the same action as returning YES. 128 | 129 | - (BOOL)shouldEndSessionAfterFirstSuccessfulScan; 130 | 131 | ## Properties 132 | Customize RMScannerView with various animations and logging. Properties automatically default to NO. To enable a continuous scan laser animation, set the `animateScanner` property to YES. To enable advanced / verbose logging, set the `verboseLogging` property to YES. 133 | 134 | RMScannerView can also display a box around a scanned barcode (provided the `shouldEndSessionAfterFirstSuccessfulScan` delegate returns NO) by setting the `displayCodeOutline` property to YES. 135 | -------------------------------------------------------------------------------- /Scanner App/Scanner App.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 992030341890AC29004AD946 /* RMOutlineBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 992030331890AC29004AD946 /* RMOutlineBox.m */; }; 11 | 994BB5971865426A005F9EAE /* ScanBanner.png in Resources */ = {isa = PBXBuildFile; fileRef = 994BB5961865426A005F9EAE /* ScanBanner.png */; }; 12 | 994BB59A18654271005F9EAE /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = 994BB59818654271005F9EAE /* LICENSE.md */; }; 13 | 994BB59B18654271005F9EAE /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 994BB59918654271005F9EAE /* README.md */; }; 14 | 99A314E41850253F000917FA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99A314E31850253F000917FA /* Foundation.framework */; }; 15 | 99A314E618502540000917FA /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99A314E518502540000917FA /* CoreGraphics.framework */; }; 16 | 99A314E818502540000917FA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99A314E718502540000917FA /* UIKit.framework */; }; 17 | 99A314EE18502540000917FA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 99A314EC18502540000917FA /* InfoPlist.strings */; }; 18 | 99A314F018502540000917FA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 99A314EF18502540000917FA /* main.m */; }; 19 | 99A314F418502540000917FA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 99A314F318502540000917FA /* AppDelegate.m */; }; 20 | 99A314F718502540000917FA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 99A314F518502540000917FA /* Main.storyboard */; }; 21 | 99A314FA18502540000917FA /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 99A314F918502540000917FA /* ViewController.m */; }; 22 | 99A314FC18502540000917FA /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 99A314FB18502540000917FA /* Images.xcassets */; }; 23 | 99A3151A18502551000917FA /* RMScannerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 99A3151918502551000917FA /* RMScannerView.m */; }; 24 | 99A3151C18502576000917FA /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99A3151B18502576000917FA /* AVFoundation.framework */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 992030321890AC29004AD946 /* RMOutlineBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.objj.h; name = RMOutlineBox.h; path = ../RMScannerView/RMOutlineBox.h; sourceTree = ""; }; 29 | 992030331890AC29004AD946 /* RMOutlineBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RMOutlineBox.m; path = ../RMScannerView/RMOutlineBox.m; sourceTree = ""; }; 30 | 994BB5961865426A005F9EAE /* ScanBanner.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ScanBanner.png; path = ../../ScanBanner.png; sourceTree = ""; }; 31 | 994BB59818654271005F9EAE /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE.md; path = ../../LICENSE.md; sourceTree = ""; }; 32 | 994BB59918654271005F9EAE /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = README.md; path = ../../README.md; sourceTree = ""; }; 33 | 99A314E01850253F000917FA /* Scanner App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Scanner App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 99A314E31850253F000917FA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 35 | 99A314E518502540000917FA /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 36 | 99A314E718502540000917FA /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 37 | 99A314EB18502540000917FA /* Scanner App-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Scanner App-Info.plist"; sourceTree = ""; }; 38 | 99A314ED18502540000917FA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 39 | 99A314EF18502540000917FA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 40 | 99A314F118502540000917FA /* Scanner App-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Scanner App-Prefix.pch"; sourceTree = ""; }; 41 | 99A314F218502540000917FA /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 42 | 99A314F318502540000917FA /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 43 | 99A314F618502540000917FA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 44 | 99A314F818502540000917FA /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 45 | 99A314F918502540000917FA /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 46 | 99A314FB18502540000917FA /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 47 | 99A3151818502551000917FA /* RMScannerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.objj.h; name = RMScannerView.h; path = ../RMScannerView/RMScannerView.h; sourceTree = ""; }; 48 | 99A3151918502551000917FA /* RMScannerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RMScannerView.m; path = ../RMScannerView/RMScannerView.m; sourceTree = ""; }; 49 | 99A3151B18502576000917FA /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 99A314DD1850253F000917FA /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 99A3151C18502576000917FA /* AVFoundation.framework in Frameworks */, 58 | 99A314E618502540000917FA /* CoreGraphics.framework in Frameworks */, 59 | 99A314E818502540000917FA /* UIKit.framework in Frameworks */, 60 | 99A314E41850253F000917FA /* Foundation.framework in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 994BB59518654260005F9EAE /* Images */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 994BB5961865426A005F9EAE /* ScanBanner.png */, 71 | ); 72 | name = Images; 73 | sourceTree = ""; 74 | }; 75 | 99A314D71850253F000917FA = { 76 | isa = PBXGroup; 77 | children = ( 78 | 99A3151818502551000917FA /* RMScannerView.h */, 79 | 99A3151918502551000917FA /* RMScannerView.m */, 80 | 992030321890AC29004AD946 /* RMOutlineBox.h */, 81 | 992030331890AC29004AD946 /* RMOutlineBox.m */, 82 | 99A314E918502540000917FA /* Scanner App */, 83 | 99A314E21850253F000917FA /* Frameworks */, 84 | 99A314E11850253F000917FA /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | 99A314E11850253F000917FA /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 99A314E01850253F000917FA /* Scanner App.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | 99A314E21850253F000917FA /* Frameworks */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 99A3151B18502576000917FA /* AVFoundation.framework */, 100 | 99A314E31850253F000917FA /* Foundation.framework */, 101 | 99A314E518502540000917FA /* CoreGraphics.framework */, 102 | 99A314E718502540000917FA /* UIKit.framework */, 103 | ); 104 | name = Frameworks; 105 | sourceTree = ""; 106 | }; 107 | 99A314E918502540000917FA /* Scanner App */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 99A314F218502540000917FA /* AppDelegate.h */, 111 | 99A314F318502540000917FA /* AppDelegate.m */, 112 | 99A314F518502540000917FA /* Main.storyboard */, 113 | 99A314F818502540000917FA /* ViewController.h */, 114 | 99A314F918502540000917FA /* ViewController.m */, 115 | 99A314FB18502540000917FA /* Images.xcassets */, 116 | 99A314EA18502540000917FA /* Supporting Files */, 117 | ); 118 | path = "Scanner App"; 119 | sourceTree = ""; 120 | }; 121 | 99A314EA18502540000917FA /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 994BB59518654260005F9EAE /* Images */, 125 | 994BB59818654271005F9EAE /* LICENSE.md */, 126 | 994BB59918654271005F9EAE /* README.md */, 127 | 99A314EB18502540000917FA /* Scanner App-Info.plist */, 128 | 99A314EC18502540000917FA /* InfoPlist.strings */, 129 | 99A314EF18502540000917FA /* main.m */, 130 | 99A314F118502540000917FA /* Scanner App-Prefix.pch */, 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | /* End PBXGroup section */ 136 | 137 | /* Begin PBXNativeTarget section */ 138 | 99A314DF1850253F000917FA /* Scanner App */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = 99A3151218502540000917FA /* Build configuration list for PBXNativeTarget "Scanner App" */; 141 | buildPhases = ( 142 | 99A314DC1850253F000917FA /* Sources */, 143 | 99A314DD1850253F000917FA /* Frameworks */, 144 | 99A314DE1850253F000917FA /* Resources */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | ); 150 | name = "Scanner App"; 151 | productName = "Scanner App"; 152 | productReference = 99A314E01850253F000917FA /* Scanner App.app */; 153 | productType = "com.apple.product-type.application"; 154 | }; 155 | /* End PBXNativeTarget section */ 156 | 157 | /* Begin PBXProject section */ 158 | 99A314D81850253F000917FA /* Project object */ = { 159 | isa = PBXProject; 160 | attributes = { 161 | LastUpgradeCheck = 0500; 162 | ORGANIZATIONNAME = "iRare Media"; 163 | }; 164 | buildConfigurationList = 99A314DB1850253F000917FA /* Build configuration list for PBXProject "Scanner App" */; 165 | compatibilityVersion = "Xcode 3.2"; 166 | developmentRegion = English; 167 | hasScannedForEncodings = 0; 168 | knownRegions = ( 169 | en, 170 | Base, 171 | ); 172 | mainGroup = 99A314D71850253F000917FA; 173 | productRefGroup = 99A314E11850253F000917FA /* Products */; 174 | projectDirPath = ""; 175 | projectRoot = ""; 176 | targets = ( 177 | 99A314DF1850253F000917FA /* Scanner App */, 178 | ); 179 | }; 180 | /* End PBXProject section */ 181 | 182 | /* Begin PBXResourcesBuildPhase section */ 183 | 99A314DE1850253F000917FA /* Resources */ = { 184 | isa = PBXResourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 994BB59B18654271005F9EAE /* README.md in Resources */, 188 | 994BB59A18654271005F9EAE /* LICENSE.md in Resources */, 189 | 994BB5971865426A005F9EAE /* ScanBanner.png in Resources */, 190 | 99A314FC18502540000917FA /* Images.xcassets in Resources */, 191 | 99A314EE18502540000917FA /* InfoPlist.strings in Resources */, 192 | 99A314F718502540000917FA /* Main.storyboard in Resources */, 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXResourcesBuildPhase section */ 197 | 198 | /* Begin PBXSourcesBuildPhase section */ 199 | 99A314DC1850253F000917FA /* Sources */ = { 200 | isa = PBXSourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | 99A314FA18502540000917FA /* ViewController.m in Sources */, 204 | 99A3151A18502551000917FA /* RMScannerView.m in Sources */, 205 | 99A314F418502540000917FA /* AppDelegate.m in Sources */, 206 | 99A314F018502540000917FA /* main.m in Sources */, 207 | 992030341890AC29004AD946 /* RMOutlineBox.m in Sources */, 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXSourcesBuildPhase section */ 212 | 213 | /* Begin PBXVariantGroup section */ 214 | 99A314EC18502540000917FA /* InfoPlist.strings */ = { 215 | isa = PBXVariantGroup; 216 | children = ( 217 | 99A314ED18502540000917FA /* en */, 218 | ); 219 | name = InfoPlist.strings; 220 | sourceTree = ""; 221 | }; 222 | 99A314F518502540000917FA /* Main.storyboard */ = { 223 | isa = PBXVariantGroup; 224 | children = ( 225 | 99A314F618502540000917FA /* Base */, 226 | ); 227 | name = Main.storyboard; 228 | sourceTree = ""; 229 | }; 230 | /* End PBXVariantGroup section */ 231 | 232 | /* Begin XCBuildConfiguration section */ 233 | 99A3151018502540000917FA /* Debug */ = { 234 | isa = XCBuildConfiguration; 235 | buildSettings = { 236 | ALWAYS_SEARCH_USER_PATHS = NO; 237 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 238 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 239 | CLANG_CXX_LIBRARY = "libc++"; 240 | CLANG_ENABLE_MODULES = YES; 241 | CLANG_ENABLE_OBJC_ARC = YES; 242 | CLANG_WARN_BOOL_CONVERSION = YES; 243 | CLANG_WARN_CONSTANT_CONVERSION = YES; 244 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 245 | CLANG_WARN_EMPTY_BODY = YES; 246 | CLANG_WARN_ENUM_CONVERSION = YES; 247 | CLANG_WARN_INT_CONVERSION = YES; 248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 249 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 250 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 251 | COPY_PHASE_STRIP = NO; 252 | GCC_C_LANGUAGE_STANDARD = gnu99; 253 | GCC_DYNAMIC_NO_PIC = NO; 254 | GCC_OPTIMIZATION_LEVEL = 0; 255 | GCC_PREPROCESSOR_DEFINITIONS = ( 256 | "DEBUG=1", 257 | "$(inherited)", 258 | ); 259 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 260 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 261 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 262 | GCC_WARN_UNDECLARED_SELECTOR = YES; 263 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 264 | GCC_WARN_UNUSED_FUNCTION = YES; 265 | GCC_WARN_UNUSED_VARIABLE = YES; 266 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 267 | ONLY_ACTIVE_ARCH = YES; 268 | SDKROOT = iphoneos; 269 | }; 270 | name = Debug; 271 | }; 272 | 99A3151118502540000917FA /* Release */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | ALWAYS_SEARCH_USER_PATHS = NO; 276 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 277 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 278 | CLANG_CXX_LIBRARY = "libc++"; 279 | CLANG_ENABLE_MODULES = YES; 280 | CLANG_ENABLE_OBJC_ARC = YES; 281 | CLANG_WARN_BOOL_CONVERSION = YES; 282 | CLANG_WARN_CONSTANT_CONVERSION = YES; 283 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 284 | CLANG_WARN_EMPTY_BODY = YES; 285 | CLANG_WARN_ENUM_CONVERSION = YES; 286 | CLANG_WARN_INT_CONVERSION = YES; 287 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 289 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 290 | COPY_PHASE_STRIP = YES; 291 | ENABLE_NS_ASSERTIONS = NO; 292 | GCC_C_LANGUAGE_STANDARD = gnu99; 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 300 | SDKROOT = iphoneos; 301 | VALIDATE_PRODUCT = YES; 302 | }; 303 | name = Release; 304 | }; 305 | 99A3151318502540000917FA /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 309 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 310 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 311 | GCC_PREFIX_HEADER = "Scanner App/Scanner App-Prefix.pch"; 312 | INFOPLIST_FILE = "Scanner App/Scanner App-Info.plist"; 313 | PRODUCT_NAME = "$(TARGET_NAME)"; 314 | WRAPPER_EXTENSION = app; 315 | }; 316 | name = Debug; 317 | }; 318 | 99A3151418502540000917FA /* Release */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 322 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 323 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 324 | GCC_PREFIX_HEADER = "Scanner App/Scanner App-Prefix.pch"; 325 | INFOPLIST_FILE = "Scanner App/Scanner App-Info.plist"; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | WRAPPER_EXTENSION = app; 328 | }; 329 | name = Release; 330 | }; 331 | /* End XCBuildConfiguration section */ 332 | 333 | /* Begin XCConfigurationList section */ 334 | 99A314DB1850253F000917FA /* Build configuration list for PBXProject "Scanner App" */ = { 335 | isa = XCConfigurationList; 336 | buildConfigurations = ( 337 | 99A3151018502540000917FA /* Debug */, 338 | 99A3151118502540000917FA /* Release */, 339 | ); 340 | defaultConfigurationIsVisible = 0; 341 | defaultConfigurationName = Release; 342 | }; 343 | 99A3151218502540000917FA /* Build configuration list for PBXNativeTarget "Scanner App" */ = { 344 | isa = XCConfigurationList; 345 | buildConfigurations = ( 346 | 99A3151318502540000917FA /* Debug */, 347 | 99A3151418502540000917FA /* Release */, 348 | ); 349 | defaultConfigurationIsVisible = 0; 350 | defaultConfigurationName = Release; 351 | }; 352 | /* End XCConfigurationList section */ 353 | }; 354 | rootObject = 99A314D81850253F000917FA /* Project object */; 355 | } 356 | -------------------------------------------------------------------------------- /RMScannerView/RMScannerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMScannerView.m 3 | // RMScannerView 4 | // 5 | // Created by iRare Media on 12/3/13. 6 | // Copyright (c) 2014 iRare Media. All rights reserved. 7 | // 8 | 9 | #import "RMScannerView.h" 10 | 11 | @interface RMScannerView () { 12 | AVCaptureDeviceInput *videoInput; 13 | AVCaptureMetadataOutput *metadataOutput; 14 | AVCaptureVideoPreviewLayer *previewLayer; 15 | UIView *laserView; 16 | RMOutlineBox *boundingBox; 17 | } 18 | 19 | - (void)initialize; 20 | 21 | - (void)setupMetadataOutput; 22 | - (void)breakdownMetadataOutput; 23 | - (void)setupPreviewLayer; 24 | 25 | - (void)setupCameraFocus; 26 | - (void)stopCameraFlash; 27 | 28 | @end 29 | 30 | @implementation RMScannerView 31 | @synthesize delegate, verboseLogging, animateScanner, displayCodeOutline; 32 | 33 | #pragma mark - Initialize 34 | 35 | - (void)initialize { 36 | self.defaultCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; 37 | self.enableAutorotation = YES; 38 | self.captureSession = [[AVCaptureSession alloc] init]; 39 | [[NSNotificationCenter defaultCenter] 40 | addObserver:self 41 | selector:@selector(setScannerViewOrientation:) 42 | name:UIDeviceOrientationDidChangeNotification 43 | object:nil]; 44 | self->_scannerLineColor = [UIColor redColor]; 45 | } 46 | 47 | - (id)initWithCoder:(NSCoder *)aDecoder { 48 | self = [super initWithCoder:aDecoder]; 49 | if (self) { 50 | [self initialize]; 51 | } 52 | 53 | return self; 54 | } 55 | 56 | - (id)initWithFrame:(CGRect)frame { 57 | self = [super initWithFrame:frame]; 58 | if (self) { 59 | [self initialize]; 60 | } 61 | 62 | return self; 63 | } 64 | 65 | - (void)dealloc { 66 | // Stop Running the Capture Session 67 | [self.captureSession stopRunning]; 68 | 69 | // Remove all Inputs 70 | for (AVCaptureInput *input in self.captureSession.inputs) { 71 | [self.captureSession removeInput:input]; 72 | } 73 | 74 | // Remove all Outputs 75 | for (AVCaptureOutput *output in self.captureSession.outputs) { 76 | [self.captureSession removeOutput:output]; 77 | } 78 | 79 | // Remove preview layer 80 | [previewLayer removeFromSuperlayer]; 81 | 82 | // Set objects to nil 83 | self.captureSession = nil; 84 | metadataOutput = nil; 85 | videoInput = nil; 86 | previewLayer = nil; 87 | 88 | // Remove orient observer 89 | [[NSNotificationCenter defaultCenter] 90 | removeObserver:self 91 | name:UIDeviceOrientationDidChangeNotification 92 | object:nil]; 93 | } 94 | 95 | #pragma mark - Scanner Checks 96 | 97 | - (BOOL)isScanSessionInProgress { 98 | if ([self.captureSession isRunning] == YES) { 99 | if ([self.captureSession.outputs containsObject:metadataOutput] == YES) return YES; 100 | else return NO; 101 | } else { 102 | return NO; 103 | } 104 | } 105 | 106 | - (BOOL)isCaptureSessionInProgress { 107 | if ([self.captureSession isRunning] == YES) return YES; 108 | else return NO; 109 | } 110 | 111 | #pragma mark - Setup and Breakdown 112 | 113 | - (void)startCaptureSession { 114 | // Log capture session start 115 | if (verboseLogging) NSLog(@"[RMScannerView] Starting Capture Session..."); 116 | 117 | NSError *error = nil; 118 | AVCaptureDevice *videoCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 119 | videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoCaptureDevice error:&error]; 120 | 121 | // Log video check 122 | if (verboseLogging) NSLog(@"[RMScannerView] Checking for video input..."); 123 | 124 | if (videoInput) { 125 | // Log video input 126 | if (verboseLogging) NSLog(@"[RMScannerView] Valid video input"); 127 | 128 | // Get the video feed from the camera and add it as an input - as long as we're not already getting a feed 129 | if ([self.captureSession.inputs containsObject:videoInput] == NO) [self.captureSession addInput:videoInput]; 130 | 131 | // Log metadata setup 132 | if (verboseLogging) NSLog(@"[RMScannerView] Setting up metadata output..."); 133 | 134 | // Start the metadata output 135 | [self setupMetadataOutput]; 136 | 137 | // Log preview layer creation 138 | if (verboseLogging) NSLog(@"[RMScannerView] Creating the preview layer..."); 139 | 140 | // Create the preview layer 141 | [self setupPreviewLayer]; 142 | 143 | // Setup Camera Focus 144 | [self setupCameraFocus]; 145 | 146 | // Check if the capture session is already running, if it is don't start it again 147 | if ([self.captureSession isRunning] == NO) [self.captureSession startRunning]; 148 | 149 | // Log capture session 150 | if (verboseLogging) NSLog(@"[RMScannerView] Started Capture Session"); 151 | 152 | // Start Scan 153 | [self startScanSession]; 154 | 155 | } else { 156 | NSLog(@"[RMScannerView] Invalid video input. There is no video input, or the scanner was not able to obtain input."); 157 | NSLog(@"[RMScannerView] %@", error); 158 | if ([self.delegate respondsToSelector:@selector(errorGeneratingCaptureSession:)]) 159 | [self.delegate errorGeneratingCaptureSession:error]; 160 | } 161 | } 162 | 163 | - (void)stopCaptureSession { 164 | // Stop the scan session 165 | [self stopScanSession]; 166 | 167 | // Remove the box 168 | [UIView animateWithDuration:0.2 animations:^{ 169 | boundingBox.alpha = 0.0; 170 | }]; 171 | 172 | // Stop the capture session 173 | [self.captureSession stopRunning]; 174 | 175 | // Log the stop 176 | if (verboseLogging) NSLog(@"[RMScannerView] Stopped capture session"); 177 | } 178 | 179 | - (void)startScanSession { 180 | if (verboseLogging) NSLog(@"[RMScannerView] Starting Scan Session..."); 181 | 182 | if (([self isCaptureSessionInProgress] == YES) && ([self isScanSessionInProgress] == NO)) { 183 | if (verboseLogging) NSLog(@"[RMScannerView] Capture session is in progress, but not a scan session. Begginning scan session..."); 184 | 185 | // Add the metadata output to the session - there is a running capture session, but no scan session 186 | [self setupMetadataOutput]; 187 | 188 | if (verboseLogging) NSLog(@"[RMScannerView] Scan session started"); 189 | } else if ([self isCaptureSessionInProgress] == NO) { 190 | if (verboseLogging) NSLog(@"[RMScannerView] Capture session not in progress, starting new session"); 191 | 192 | // The capture session may not be running, start it 193 | [self startCaptureSession]; 194 | } 195 | 196 | // Begin the capture session animations 197 | if (animateScanner) { 198 | // Add the view to draw the bounding box for the UIView 199 | boundingBox = [[RMOutlineBox alloc] initWithFrame:self.bounds]; 200 | boundingBox.alpha = 0.0; 201 | [self addSubview:boundingBox]; 202 | 203 | if (!laserView) laserView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 2)]; 204 | laserView.backgroundColor = self.scannerLineColor; 205 | laserView.layer.shadowColor = self.scannerLineColor.CGColor; 206 | laserView.layer.shadowOffset = CGSizeMake(0.5, 0.5); 207 | laserView.layer.shadowOpacity = 0.6; 208 | laserView.layer.shadowRadius = 1.5; 209 | laserView.alpha = 0.0; 210 | if (![[self subviews] containsObject:laserView]) [self addSubview:laserView]; 211 | 212 | // Add the line 213 | [UIView animateWithDuration:0.2 animations:^{ 214 | laserView.alpha = 1.0; 215 | }]; 216 | 217 | [UIView animateWithDuration:4.0 delay:0.0 options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut animations:^{ 218 | laserView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, 2); 219 | } completion:nil]; 220 | } 221 | } 222 | 223 | - (void)stopScanSession { 224 | // Remove the line 225 | [UIView animateWithDuration:0.2 animations:^{ 226 | laserView.alpha = 0.0; 227 | }]; 228 | 229 | // Breakdown the metadata output 230 | [self breakdownMetadataOutput]; 231 | 232 | // Stop the camera flash 233 | [self stopCameraFlash]; 234 | 235 | // Log the stop 236 | if (verboseLogging) NSLog(@"[RMScannerView] Stopped scan session"); 237 | } 238 | 239 | - (void)setupMetadataOutput { 240 | // Log the metadata object 241 | if (verboseLogging) NSLog(@"[RMScannerView] Creating metadata object"); 242 | 243 | if (metadataOutput == nil) metadataOutput = [[AVCaptureMetadataOutput alloc] init]; // Setup the metadata object if it doesn't already exist 244 | if ([self.captureSession.outputs containsObject:metadataOutput] == NO) [self.captureSession addOutput:metadataOutput]; // Add the metadata object if it hasn't been added 245 | [metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; 246 | [metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeAztecCode, AVMetadataObjectTypeCode128Code, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode39Mod43Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeQRCode, AVMetadataObjectTypeUPCECode]]; 247 | } 248 | 249 | - (void)breakdownMetadataOutput { 250 | // Log the breakdown 251 | if (verboseLogging) NSLog(@"[RMScannerView] Breaking down metadata output"); 252 | 253 | // Remove the metadata output from the capture session 254 | if ([self.captureSession.outputs containsObject:metadataOutput] == YES) [self.captureSession removeOutput:metadataOutput]; 255 | } 256 | 257 | - (void)setupPreviewLayer { 258 | // Log the creation of the preview layer 259 | if (verboseLogging) NSLog(@"[RMScannerView] Creating the preview layer"); 260 | 261 | // Create the preview layer to display the video on 262 | if (previewLayer == nil) previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession]; 263 | previewLayer.frame = self.layer.bounds; 264 | previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 265 | previewLayer.position = CGPointMake(CGRectGetMidX(self.layer.bounds), CGRectGetMidY(self.layer.bounds)); 266 | AVCaptureVideoOrientation videoOrientation = _enableAutorotation ? ((AVCaptureVideoOrientation)[[UIDevice currentDevice] orientation]) :_defaultCaptureVideoOrientation; 267 | [[previewLayer connection] setVideoOrientation:videoOrientation]; 268 | if ([self.layer.sublayers containsObject:previewLayer] == NO) [self.layer addSublayer:previewLayer]; 269 | } 270 | 271 | #pragma mark - Camera Focus 272 | 273 | - (void)setupCameraFocus { 274 | // Grab the current device from the current capture session 275 | AVCaptureDevice *device = videoInput.device; 276 | 277 | // Create the error object 278 | NSError *error; 279 | 280 | // Lock the hardware configuration to prevent other apps from changing the configuration 281 | if ([device lockForConfiguration:&error]) { 282 | 283 | // Check if auto focus is supported 284 | if ([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) { 285 | // Auto-focus the camera 286 | [device setFocusMode:AVCaptureFocusModeAutoFocus]; 287 | } 288 | 289 | // Check if auto focus range restruction is supported 290 | if ([device isAutoFocusRangeRestrictionSupported]) { 291 | // Configure auto-focus for near objects 292 | [device setAutoFocusRangeRestriction:AVCaptureAutoFocusRangeRestrictionNear]; 293 | } 294 | 295 | // Unlock the hardware configuration 296 | [device unlockForConfiguration]; 297 | } else { 298 | NSLog(@"[RMScannerView] %@", error); 299 | if ([[self delegate] respondsToSelector:@selector(errorAcquiringDeviceHardwareLock:)]) 300 | [[self delegate] errorAcquiringDeviceHardwareLock:error]; 301 | } 302 | } 303 | 304 | #pragma mark - Camera Flash 305 | 306 | - (void)setDeviceFlash:(AVCaptureFlashMode)flashMode { 307 | // Grab the current device from the current capture session 308 | AVCaptureDevice *device = videoInput.device; 309 | 310 | // Check if flash is supported 311 | if (![device isFlashAvailable] && ![device isFlashModeSupported:flashMode]) return; 312 | 313 | // Create the error object 314 | NSError *error; 315 | 316 | // Lock the hardware configuration to prevent other apps from changing the configuration 317 | if ([device lockForConfiguration:&error]) { 318 | // Set the camera flash 319 | [device setFlashMode:flashMode]; 320 | 321 | // Unlock the hardware configuration 322 | [device unlockForConfiguration]; 323 | } else { 324 | NSLog(@"[RMScannerView] %@", error); 325 | if ([[self delegate] respondsToSelector:@selector(errorAcquiringDeviceHardwareLock:)]) 326 | [[self delegate] errorAcquiringDeviceHardwareLock:error]; 327 | } 328 | } 329 | 330 | - (void)setDeviceTorch:(AVCaptureTorchMode)torchMode { 331 | // Grab the current device from the current capture session 332 | AVCaptureDevice *device = videoInput.device; 333 | 334 | // Check if flash is supported 335 | if (![device isTorchAvailable] && ![device isTorchModeSupported:torchMode]) return; 336 | 337 | // Create the error object 338 | NSError *error; 339 | 340 | // Lock the hardware configuration to prevent other apps from changing the configuration 341 | if ([device lockForConfiguration:&error]) { 342 | // Set the camera flash 343 | [device setTorchMode:torchMode]; 344 | 345 | // Unlock the hardware configuration 346 | [device unlockForConfiguration]; 347 | } else { 348 | NSLog(@"[RMScannerView] %@", error); 349 | if ([[self delegate] respondsToSelector:@selector(errorAcquiringDeviceHardwareLock:)]) 350 | [[self delegate] errorAcquiringDeviceHardwareLock:error]; 351 | } 352 | } 353 | 354 | - (void)stopCameraFlash { 355 | // Grab the current device from the current capture session 356 | AVCaptureDevice *device = videoInput.device; 357 | 358 | // Check if flash is supported 359 | if (![device isFlashAvailable] 360 | && ![device isFlashActive] 361 | && ![device isTorchAvailable] 362 | && ![device isTorchActive]) return; 363 | 364 | // Create the error object 365 | NSError *error; 366 | 367 | // Lock the hardware configuration to prevent other apps from changing the configuration 368 | if ([device lockForConfiguration:&error]) { 369 | // Set the camera flash 370 | if (device.isFlashAvailable && device.isFlashActive) 371 | { 372 | [device setFlashMode:AVCaptureFlashModeOff]; 373 | } 374 | if (device.isTorchActive && device.isTorchActive) 375 | { 376 | [device setTorchMode:AVCaptureTorchModeOff]; 377 | } 378 | 379 | // Unlock the hardware configuration 380 | [device unlockForConfiguration]; 381 | } else { 382 | NSLog(@"[RMScannerView] %@", error); 383 | if ([[self delegate] respondsToSelector:@selector(errorAcquiringDeviceHardwareLock:)]) 384 | [[self delegate] errorAcquiringDeviceHardwareLock:error]; 385 | } 386 | } 387 | 388 | #pragma mark - Touch Gestures 389 | 390 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 391 | // Get the point that the user touched at 392 | UITouch *touch = [touches anyObject]; 393 | CGPoint touchPoint = [touch locationInView:self]; 394 | 395 | // Grab the current device from the current capture session 396 | AVCaptureDevice *device = videoInput.device; 397 | 398 | // Check if auto focus is supported 399 | if (![device isFocusPointOfInterestSupported] && ![device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) return; 400 | 401 | // Create the error object 402 | NSError *error; 403 | 404 | // Lock the hardware configuration to prevent other apps from changing the configuration 405 | if ([device lockForConfiguration:&error]) { 406 | // Auto-focus the camera to the point the user touched 407 | [device setFocusPointOfInterest:touchPoint]; 408 | [device setFocusMode:AVCaptureFocusModeAutoFocus]; 409 | 410 | // Unlock the hardware configuration 411 | [device unlockForConfiguration]; 412 | } else { 413 | NSLog(@"[RMScannerView] %@", error); 414 | if ([[self delegate] respondsToSelector:@selector(errorAcquiringDeviceHardwareLock:)]) 415 | [[self delegate] errorAcquiringDeviceHardwareLock:error]; 416 | } 417 | } 418 | 419 | #pragma mark - Output handling 420 | 421 | - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection { 422 | // A barcode was scanned, report the scan 423 | for (AVMetadataObject *metadataObject in metadataObjects) { 424 | 425 | // Log the object 426 | if (verboseLogging) NSLog(@"[RMScannerView] Scanned metadata object: %@", metadataObject); 427 | 428 | // Get the AVMetadataMachineReadableCodeObject 429 | AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)metadataObject; 430 | AVMetadataMachineReadableCodeObject *transformedObject = (AVMetadataMachineReadableCodeObject *)[previewLayer transformedMetadataObjectForMetadataObject:metadataObject]; 431 | 432 | // Call the delegate to report the scan 433 | if ([self.delegate respondsToSelector:@selector(didScanCode:onCodeType:)]) 434 | [self.delegate didScanCode:readableObject.stringValue onCodeType:readableObject.type]; 435 | 436 | // Call the delegate to check if scanning is continuous or if it should be stopped after the first scan 437 | if ([self.delegate respondsToSelector:@selector(shouldEndSessionAfterFirstSuccessfulScan)]) { 438 | BOOL shouldEndSession = [self.delegate shouldEndSessionAfterFirstSuccessfulScan]; 439 | if (shouldEndSession == YES) { 440 | [self stopScanSession]; 441 | } else { 442 | if (displayCodeOutline) { 443 | // Update the frame on the boundingBox view, and show it 444 | [UIView animateWithDuration:0.2 animations:^{ 445 | laserView.alpha = 0.0; 446 | boundingBox.frame = transformedObject.bounds; 447 | boundingBox.alpha = 1.0; 448 | boundingBox.corners = [self translatePoints:transformedObject.corners fromView:self toView:boundingBox]; 449 | }]; 450 | 451 | [UIView animateWithDuration:0.5 delay:2.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 452 | laserView.alpha = 1.0; 453 | boundingBox.alpha = 0.0; 454 | } completion:nil]; 455 | } 456 | 457 | continue; 458 | } 459 | } else { 460 | [self stopScanSession]; 461 | } 462 | } 463 | } 464 | 465 | - (NSString *)humanReadableCodeTypeForCode:(NSString *)codeType { 466 | if (codeType == AVMetadataObjectTypeAztecCode) return @"Aztec"; 467 | if (codeType == AVMetadataObjectTypeCode128Code) return @"Code 128"; 468 | if (codeType == AVMetadataObjectTypeCode39Code) return @"Code 39"; 469 | if (codeType == AVMetadataObjectTypeCode39Mod43Code) return @"Code 39 Mod 43"; 470 | if (codeType == AVMetadataObjectTypeCode93Code) return @"Code 93"; 471 | if (codeType == AVMetadataObjectTypeEAN13Code) return @"EAN13"; 472 | if (codeType == AVMetadataObjectTypeEAN8Code) return @"EAN8"; 473 | if (codeType == AVMetadataObjectTypePDF417Code) return @"PDF417"; 474 | if (codeType == AVMetadataObjectTypeQRCode) return @"QR"; 475 | if (codeType == AVMetadataObjectTypeUPCECode) return @"UPCE"; 476 | 477 | return nil; 478 | } 479 | 480 | - (NSArray *)translatePoints:(NSArray *)points fromView:(UIView *)fromView toView:(UIView *)toView { 481 | NSMutableArray *translatedPoints = [NSMutableArray new]; 482 | 483 | // The points are provided in a dictionary with keys X and Y 484 | for (NSDictionary *point in points) { 485 | // Let's turn them into CGPoints 486 | CGPoint pointValue = CGPointMake([point[@"X"] floatValue], [point[@"Y"] floatValue]); 487 | 488 | // Now translate from one view to the other 489 | CGPoint translatedPoint = [fromView convertPoint:pointValue toView:toView]; 490 | 491 | // Box them up and add to the array 492 | [translatedPoints addObject:[NSValue valueWithCGPoint:translatedPoint]]; 493 | } 494 | 495 | return [translatedPoints copy]; 496 | } 497 | 498 | -(void)setScannerViewOrientation:(UIDeviceOrientation)toDeviceOrientation 499 | { 500 | if (previewLayer) { 501 | AVCaptureVideoOrientation videoOrientation = _enableAutorotation ? ((AVCaptureVideoOrientation)[[UIDevice currentDevice] orientation]) :_defaultCaptureVideoOrientation; 502 | [[previewLayer connection] setVideoOrientation:videoOrientation]; 503 | } 504 | } 505 | 506 | @end 507 | --------------------------------------------------------------------------------