├── 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 |
--------------------------------------------------------------------------------