[REFACTORING] Prepare for switch camera feature
This commit is contained in:
parent
30648714b2
commit
43edacc4ad
|
@ -13,7 +13,8 @@
|
|||
CE412E9619D9A1E4000F294E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE412E9519D9A1E4000F294E /* Images.xcassets */; };
|
||||
CE412E9919D9A1E4000F294E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE412E9719D9A1E4000F294E /* LaunchScreen.xib */; };
|
||||
CE412EA519D9A1E4000F294E /* QRCodeReader_swiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE412EA419D9A1E4000F294E /* QRCodeReader_swiftTests.swift */; };
|
||||
CE412EAF19D9A252000F294E /* QRCodeReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE412EAE19D9A252000F294E /* QRCodeReader.swift */; };
|
||||
CED23DDC1A15079300BE7A72 /* QRCodeReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED23DDB1A15079300BE7A72 /* QRCodeReader.swift */; };
|
||||
CED23DDE1A1507CB00BE7A72 /* SwitchCameraButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED23DDD1A1507CB00BE7A72 /* SwitchCameraButton.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -37,8 +38,9 @@
|
|||
CE412E9E19D9A1E4000F294E /* QRCodeReader.swiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QRCodeReader.swiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CE412EA319D9A1E4000F294E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
CE412EA419D9A1E4000F294E /* QRCodeReader_swiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeReader_swiftTests.swift; sourceTree = "<group>"; };
|
||||
CE412EAE19D9A252000F294E /* QRCodeReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QRCodeReader.swift; path = ../QRCodeReader.swift; sourceTree = "<group>"; };
|
||||
CEC20A861A14EF0D00E7D0AD /* CameraSwitchIcon.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = CameraSwitchIcon.playground; path = resources/CameraSwitchIcon.playground; sourceTree = "<group>"; };
|
||||
CED23DDB1A15079300BE7A72 /* QRCodeReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeReader.swift; sourceTree = "<group>"; };
|
||||
CED23DDD1A1507CB00BE7A72 /* SwitchCameraButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchCameraButton.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -62,10 +64,10 @@
|
|||
CE412E8019D9A1E4000F294E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CED23DDA1A15079300BE7A72 /* QRCodeReader */,
|
||||
CE412E8B19D9A1E4000F294E /* Example */,
|
||||
CE412EA119D9A1E4000F294E /* ExampleTests */,
|
||||
CEC20A871A14EF1400E7D0AD /* Resources */,
|
||||
CE412EAE19D9A252000F294E /* QRCodeReader.swift */,
|
||||
CE412E8B19D9A1E4000F294E /* QRCodeReader.swift */,
|
||||
CE412EA119D9A1E4000F294E /* QRCodeReader.swiftTests */,
|
||||
CE412E8A19D9A1E4000F294E /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
|
@ -79,7 +81,7 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE412E8B19D9A1E4000F294E /* QRCodeReader.swift */ = {
|
||||
CE412E8B19D9A1E4000F294E /* Example */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE412E8E19D9A1E4000F294E /* AppDelegate.swift */,
|
||||
|
@ -89,6 +91,7 @@
|
|||
CE412E9719D9A1E4000F294E /* LaunchScreen.xib */,
|
||||
CE412E8C19D9A1E4000F294E /* Supporting Files */,
|
||||
);
|
||||
name = Example;
|
||||
path = QRCodeReader.swift;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -100,12 +103,13 @@
|
|||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE412EA119D9A1E4000F294E /* QRCodeReader.swiftTests */ = {
|
||||
CE412EA119D9A1E4000F294E /* ExampleTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE412EA419D9A1E4000F294E /* QRCodeReader_swiftTests.swift */,
|
||||
CE412EA219D9A1E4000F294E /* Supporting Files */,
|
||||
);
|
||||
name = ExampleTests;
|
||||
path = QRCodeReader.swiftTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -125,6 +129,16 @@
|
|||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CED23DDA1A15079300BE7A72 /* QRCodeReader */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CED23DDB1A15079300BE7A72 /* QRCodeReader.swift */,
|
||||
CED23DDD1A1507CB00BE7A72 /* SwitchCameraButton.swift */,
|
||||
);
|
||||
name = QRCodeReader;
|
||||
path = ../QRCodeReader;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -226,7 +240,8 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CE412E9119D9A1E4000F294E /* ViewController.swift in Sources */,
|
||||
CE412EAF19D9A252000F294E /* QRCodeReader.swift in Sources */,
|
||||
CED23DDC1A15079300BE7A72 /* QRCodeReader.swift in Sources */,
|
||||
CED23DDE1A1507CB00BE7A72 /* SwitchCameraButton.swift in Sources */,
|
||||
CE412E8F19D9A1E4000F294E /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="13F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6244"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
|
|
|
@ -3,8 +3,19 @@
|
|||
import UIKit
|
||||
import XCPlayground
|
||||
|
||||
class CameraIconView: UIView {
|
||||
|
||||
class SwitchCameraButton: UIButton {
|
||||
var strokeColor: UIColor = UIColor.whiteColor() {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
var paintColor: UIColor = UIColor.darkGrayColor() {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override func drawRect(rect: CGRect) {
|
||||
let width = rect.width
|
||||
let height = rect.height
|
||||
|
@ -13,11 +24,6 @@ class CameraIconView: UIView {
|
|||
|
||||
let strokeLineWidth = CGFloat(2)
|
||||
|
||||
// Color Declarations
|
||||
|
||||
let outerColor = UIColor.whiteColor()
|
||||
let innerColor = UIColor.grayColor()
|
||||
|
||||
// Camera box
|
||||
|
||||
let cameraWidth = width * 0.4
|
||||
|
@ -25,7 +31,7 @@ class CameraIconView: UIView {
|
|||
let cameraX = center - cameraWidth / 2
|
||||
let cameraY = middle - cameraHeight / 2
|
||||
|
||||
let boxPath = UIBezierPath(roundedRect: CGRectMake(cameraX, cameraY, cameraWidth, cameraHeight), cornerRadius: 2)
|
||||
let boxPath = UIBezierPath(roundedRect: CGRectMake(cameraX, cameraY, cameraWidth, cameraHeight), cornerRadius: 4)
|
||||
|
||||
// Camera lens
|
||||
|
||||
|
@ -96,37 +102,40 @@ class CameraIconView: UIView {
|
|||
|
||||
// Drawing
|
||||
|
||||
innerColor.setFill()
|
||||
paintColor.setFill()
|
||||
rigthArrowPath.fill()
|
||||
outerColor.setStroke()
|
||||
strokeColor.setStroke()
|
||||
rigthArrowPath.lineWidth = strokeLineWidth
|
||||
rigthArrowPath.stroke()
|
||||
|
||||
innerColor.setFill()
|
||||
paintColor.setFill()
|
||||
boxPath.fill()
|
||||
outerColor.setStroke()
|
||||
strokeColor.setStroke()
|
||||
boxPath.lineWidth = strokeLineWidth
|
||||
boxPath.stroke()
|
||||
|
||||
outerColor.setFill()
|
||||
strokeColor.setFill()
|
||||
outerLensPath.fill()
|
||||
|
||||
UIColor.grayColor().setFill()
|
||||
paintColor.setFill()
|
||||
innerLensPath.fill()
|
||||
|
||||
UIColor.grayColor().setFill()
|
||||
paintColor.setFill()
|
||||
flashPath.fill()
|
||||
strokeColor.setStroke()
|
||||
flashPath.lineWidth = strokeLineWidth
|
||||
flashPath.stroke()
|
||||
|
||||
leftArrowPath.closePath()
|
||||
UIColor.grayColor().setFill()
|
||||
paintColor.setFill()
|
||||
leftArrowPath.fill()
|
||||
outerColor.setStroke()
|
||||
strokeColor.setStroke()
|
||||
leftArrowPath.lineWidth = strokeLineWidth
|
||||
leftArrowPath.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
var view = CameraIconView(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
var view = SwitchCameraButton(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
|
||||
view.backgroundColor = UIColor.whiteColor()
|
||||
|
||||
XCPShowView("Camera Switch Icon", view)
|
||||
|
|
|
@ -1,198 +0,0 @@
|
|||
/*
|
||||
* QRCodeReader.swift
|
||||
*
|
||||
* Copyright 2014-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
class QRCodeReader: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
|
||||
private var cameraView: UIView = UIView()
|
||||
private var cancelButton: UIButton = UIButton()
|
||||
|
||||
private var device: AVCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
|
||||
private lazy var deviceInput: AVCaptureDeviceInput = { return AVCaptureDeviceInput(device: self.device, error: nil) }()
|
||||
private var metadataOutput: AVCaptureMetadataOutput = AVCaptureMetadataOutput()
|
||||
private var session: AVCaptureSession = AVCaptureSession()
|
||||
private lazy var previewLayer: AVCaptureVideoPreviewLayer = { return AVCaptureVideoPreviewLayer(session: self.session) }()
|
||||
|
||||
weak var delegate: QRCodeReaderDelegate?
|
||||
var completionBlock: ((String?) -> ())?
|
||||
|
||||
deinit {
|
||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
||||
}
|
||||
|
||||
required init(cancelButtonTitle: String) {
|
||||
super.init()
|
||||
|
||||
setupUIComponentsWithCancelButtonTitle(cancelButtonTitle)
|
||||
setupAutoLayoutConstraints()
|
||||
configureComponents()
|
||||
|
||||
view.backgroundColor = UIColor.blackColor()
|
||||
|
||||
cameraView.layer.insertSublayer(previewLayer, atIndex: 0)
|
||||
}
|
||||
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
override func viewWillAppear(animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
startScanning()
|
||||
|
||||
NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationDidChanged:", name: UIDeviceOrientationDidChangeNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(animated: Bool) {
|
||||
stopScanning()
|
||||
|
||||
super.viewWillDisappear(animated)
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
previewLayer.frame = view.bounds
|
||||
}
|
||||
|
||||
// MARK: - Managing the Orientation
|
||||
|
||||
func orientationDidChanged(notification: NSNotification) {
|
||||
var interfaceOrientation: UIInterfaceOrientation = .Portrait
|
||||
switch (UIDevice.currentDevice().orientation) {
|
||||
case .LandscapeLeft:
|
||||
interfaceOrientation = .LandscapeRight
|
||||
case .LandscapeRight:
|
||||
interfaceOrientation = .LandscapeLeft
|
||||
case .PortraitUpsideDown:
|
||||
interfaceOrientation = .PortraitUpsideDown
|
||||
default:
|
||||
interfaceOrientation = .Portrait
|
||||
}
|
||||
|
||||
previewLayer.connection.videoOrientation = QRCodeReader.videoOrientationFromInterfaceOrientation(interfaceOrientation)
|
||||
}
|
||||
|
||||
class func videoOrientationFromInterfaceOrientation(interfaceOrientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
|
||||
switch (interfaceOrientation) {
|
||||
case .LandscapeLeft:
|
||||
return .LandscapeLeft
|
||||
case .LandscapeRight:
|
||||
return .LandscapeRight
|
||||
case .Portrait:
|
||||
return .Portrait
|
||||
default:
|
||||
return .PortraitUpsideDown
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Initializing the AV Components
|
||||
|
||||
private func setupUIComponentsWithCancelButtonTitle(cancelButtonTitle: String) {
|
||||
cameraView.clipsToBounds = true
|
||||
cameraView.setTranslatesAutoresizingMaskIntoConstraints(false)
|
||||
view.addSubview(cameraView)
|
||||
|
||||
cancelButton.setTranslatesAutoresizingMaskIntoConstraints(false)
|
||||
cancelButton.setTitle(cancelButtonTitle, forState: .Normal)
|
||||
cancelButton.setTitleColor(UIColor.grayColor(), forState: .Highlighted)
|
||||
cancelButton.addTarget(self, action: "cancelAction:", forControlEvents: .TouchUpInside)
|
||||
view.addSubview(cancelButton)
|
||||
}
|
||||
|
||||
private func setupAutoLayoutConstraints() {
|
||||
let views: [NSObject: AnyObject] = [ "cameraView": cameraView, "cancelButton": cancelButton ]
|
||||
|
||||
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[cameraView][cancelButton(40)]|", options: .allZeros, metrics: nil, views: views))
|
||||
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[cameraView]|", options: .allZeros, metrics: nil, views: views))
|
||||
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[cancelButton]-|", options: .allZeros, metrics: nil, views: views))
|
||||
}
|
||||
|
||||
private func configureComponents() {
|
||||
session.addOutput(metadataOutput)
|
||||
session.addInput(deviceInput)
|
||||
metadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
|
||||
metadataOutput.metadataObjectTypes = [ AVMetadataObjectTypeQRCode ]
|
||||
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
|
||||
|
||||
if previewLayer.connection.supportsVideoOrientation {
|
||||
previewLayer.connection.videoOrientation = QRCodeReader.videoOrientationFromInterfaceOrientation(interfaceOrientation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Controlling Reader
|
||||
|
||||
private func startScanning() {
|
||||
if !session.running {
|
||||
session.startRunning()
|
||||
}
|
||||
}
|
||||
|
||||
private func stopScanning() {
|
||||
if session.running {
|
||||
session.stopRunning()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Catching Button Events
|
||||
|
||||
func cancelAction(button: UIButton) {
|
||||
stopScanning()
|
||||
|
||||
if let _completionBlock = completionBlock {
|
||||
_completionBlock(nil)
|
||||
}
|
||||
|
||||
delegate?.readerDidCancel(self)
|
||||
}
|
||||
|
||||
// MARK: - AVCaptureMetadataOutputObjects Delegate Methods
|
||||
|
||||
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
|
||||
for current in metadataObjects {
|
||||
if let _readableCodeObject = current as? AVMetadataMachineReadableCodeObject {
|
||||
if _readableCodeObject.type == AVMetadataObjectTypeQRCode {
|
||||
let scannedResult: String = _readableCodeObject.stringValue
|
||||
|
||||
if let _completionBlock = completionBlock {
|
||||
_completionBlock(scannedResult)
|
||||
}
|
||||
|
||||
delegate?.reader(self, didScanResult: scannedResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol QRCodeReaderDelegate: class {
|
||||
func reader(reader: QRCodeReader, didScanResult result: String)
|
||||
func readerDidCancel(reader: QRCodeReader)
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* QRCodeReader.swift
|
||||
*
|
||||
* Copyright 2014-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
class QRCodeReader: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
|
||||
private var cameraView: UIView = UIView()
|
||||
private var cancelButton: UIButton = UIButton()
|
||||
private var switchCameraButton: SwitchCameraButton?
|
||||
|
||||
private var defaultDevice: AVCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
|
||||
private var frontDevice: AVCaptureDevice? = {
|
||||
for device in AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) {
|
||||
if let _device = device as? AVCaptureDevice {
|
||||
if _device.position == AVCaptureDevicePosition.Front {
|
||||
return _device
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
private lazy var deviceInput: AVCaptureDeviceInput = { return AVCaptureDeviceInput(device: self.defaultDevice, error: nil) }()
|
||||
private var metadataOutput: AVCaptureMetadataOutput = AVCaptureMetadataOutput()
|
||||
private var session: AVCaptureSession = AVCaptureSession()
|
||||
private lazy var previewLayer: AVCaptureVideoPreviewLayer = { return AVCaptureVideoPreviewLayer(session: self.session) }()
|
||||
|
||||
weak var delegate: QRCodeReaderDelegate?
|
||||
var completionBlock: ((String?) -> ())?
|
||||
|
||||
deinit {
|
||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
||||
}
|
||||
|
||||
required init(cancelButtonTitle: String) {
|
||||
super.init()
|
||||
|
||||
configureComponents()
|
||||
setupUIComponentsWithCancelButtonTitle(cancelButtonTitle)
|
||||
setupAutoLayoutConstraints()
|
||||
|
||||
view.backgroundColor = UIColor.blackColor()
|
||||
|
||||
cameraView.layer.insertSublayer(previewLayer, atIndex: 0)
|
||||
}
|
||||
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
override func viewWillAppear(animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
startScanning()
|
||||
|
||||
NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationDidChanged:", name: UIDeviceOrientationDidChangeNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(animated: Bool) {
|
||||
stopScanning()
|
||||
|
||||
super.viewWillDisappear(animated)
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
previewLayer.frame = view.bounds
|
||||
}
|
||||
|
||||
// MARK: - Managing the Orientation
|
||||
|
||||
func orientationDidChanged(notification: NSNotification) {
|
||||
var interfaceOrientation: UIInterfaceOrientation = .Portrait
|
||||
switch (UIDevice.currentDevice().orientation) {
|
||||
case .LandscapeLeft:
|
||||
interfaceOrientation = .LandscapeRight
|
||||
case .LandscapeRight:
|
||||
interfaceOrientation = .LandscapeLeft
|
||||
case .PortraitUpsideDown:
|
||||
interfaceOrientation = .PortraitUpsideDown
|
||||
default:
|
||||
interfaceOrientation = .Portrait
|
||||
}
|
||||
|
||||
previewLayer.connection.videoOrientation = QRCodeReader.videoOrientationFromInterfaceOrientation(interfaceOrientation)
|
||||
}
|
||||
|
||||
class func videoOrientationFromInterfaceOrientation(interfaceOrientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
|
||||
switch (interfaceOrientation) {
|
||||
case .LandscapeLeft:
|
||||
return .LandscapeLeft
|
||||
case .LandscapeRight:
|
||||
return .LandscapeRight
|
||||
case .Portrait:
|
||||
return .Portrait
|
||||
default:
|
||||
return .PortraitUpsideDown
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Initializing the AV Components
|
||||
|
||||
private func setupUIComponentsWithCancelButtonTitle(cancelButtonTitle: String) {
|
||||
cameraView.clipsToBounds = true
|
||||
cameraView.setTranslatesAutoresizingMaskIntoConstraints(false)
|
||||
view.addSubview(cameraView)
|
||||
|
||||
if let _frontDevice = frontDevice {
|
||||
let newSwitchCameraButton = SwitchCameraButton()
|
||||
newSwitchCameraButton.setTranslatesAutoresizingMaskIntoConstraints(false)
|
||||
newSwitchCameraButton.addTarget(self, action: "switchCameraAction:", forControlEvents: .TouchUpInside)
|
||||
view.addSubview(newSwitchCameraButton)
|
||||
|
||||
switchCameraButton = newSwitchCameraButton
|
||||
}
|
||||
|
||||
cancelButton.setTranslatesAutoresizingMaskIntoConstraints(false)
|
||||
cancelButton.setTitle(cancelButtonTitle, forState: .Normal)
|
||||
cancelButton.setTitleColor(UIColor.grayColor(), forState: .Highlighted)
|
||||
cancelButton.addTarget(self, action: "cancelAction:", forControlEvents: .TouchUpInside)
|
||||
view.addSubview(cancelButton)
|
||||
}
|
||||
|
||||
private func setupAutoLayoutConstraints() {
|
||||
let views: [NSObject: AnyObject] = ["cameraView": cameraView, "cancelButton": cancelButton]
|
||||
|
||||
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[cameraView][cancelButton(40)]|", options: .allZeros, metrics: nil, views: views))
|
||||
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[cameraView]|", options: .allZeros, metrics: nil, views: views))
|
||||
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[cancelButton]-|", options: .allZeros, metrics: nil, views: views))
|
||||
|
||||
if let _switchCameraButton = switchCameraButton {
|
||||
let switchViews: [NSObject: AnyObject] = ["switchCameraButton": _switchCameraButton]
|
||||
|
||||
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[switchCameraButton(50)]", options: .allZeros, metrics: nil, views: switchViews))
|
||||
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[switchCameraButton(70)]|", options: .allZeros, metrics: nil, views: switchViews))
|
||||
}
|
||||
}
|
||||
|
||||
private func configureComponents() {
|
||||
session.addOutput(metadataOutput)
|
||||
session.addInput(deviceInput)
|
||||
metadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
|
||||
metadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
|
||||
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
|
||||
|
||||
if previewLayer.connection.supportsVideoOrientation {
|
||||
previewLayer.connection.videoOrientation = QRCodeReader.videoOrientationFromInterfaceOrientation(interfaceOrientation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Controlling Reader
|
||||
|
||||
private func startScanning() {
|
||||
if !session.running {
|
||||
session.startRunning()
|
||||
}
|
||||
}
|
||||
|
||||
private func stopScanning() {
|
||||
if session.running {
|
||||
session.stopRunning()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Catching Button Events
|
||||
|
||||
func cancelAction(button: UIButton) {
|
||||
stopScanning()
|
||||
|
||||
if let _completionBlock = completionBlock {
|
||||
_completionBlock(nil)
|
||||
}
|
||||
|
||||
delegate?.readerDidCancel(self)
|
||||
}
|
||||
|
||||
func switchCameraAction(button: SwitchCameraButton) {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AVCaptureMetadataOutputObjects Delegate Methods
|
||||
|
||||
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
|
||||
for current in metadataObjects {
|
||||
if let _readableCodeObject = current as? AVMetadataMachineReadableCodeObject {
|
||||
if _readableCodeObject.type == AVMetadataObjectTypeQRCode {
|
||||
let scannedResult: String = _readableCodeObject.stringValue
|
||||
|
||||
if let _completionBlock = completionBlock {
|
||||
_completionBlock(scannedResult)
|
||||
}
|
||||
|
||||
delegate?.reader(self, didScanResult: scannedResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol QRCodeReaderDelegate: class {
|
||||
func reader(reader: QRCodeReader, didScanResult result: String)
|
||||
func readerDidCancel(reader: QRCodeReader)
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* QRCodeReader.swift
|
||||
*
|
||||
* Copyright 2014-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable class SwitchCameraButton: UIButton {
|
||||
@IBInspectable var edgeColor: UIColor = UIColor.whiteColor() {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
@IBInspectable var fillColor: UIColor = UIColor.darkGrayColor() {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
@IBInspectable var edgeHighlightedColor: UIColor = UIColor.whiteColor()
|
||||
@IBInspectable var fillHighlightedColor: UIColor = UIColor.blackColor()
|
||||
|
||||
override func drawRect(rect: CGRect) {
|
||||
let width = rect.width
|
||||
let height = rect.height
|
||||
let center = width / 2
|
||||
let middle = height / 2
|
||||
|
||||
let strokeLineWidth = CGFloat(2)
|
||||
|
||||
// Colors
|
||||
|
||||
let paintColor = (self.state != .Highlighted) ? fillColor : fillHighlightedColor
|
||||
let strokeColor = (self.state != .Highlighted) ? edgeColor : edgeHighlightedColor
|
||||
|
||||
// Camera box
|
||||
|
||||
let cameraWidth = width * 0.4
|
||||
let cameraHeight = cameraWidth * 0.6
|
||||
let cameraX = center - cameraWidth / 2
|
||||
let cameraY = middle - cameraHeight / 2
|
||||
let cameraRadius = cameraWidth / 80
|
||||
|
||||
let boxPath = UIBezierPath(roundedRect: CGRectMake(cameraX, cameraY, cameraWidth, cameraHeight), cornerRadius: cameraRadius)
|
||||
|
||||
// Camera lens
|
||||
|
||||
let outerLensSize = cameraHeight * 0.8
|
||||
let outerLensX = center - outerLensSize / 2
|
||||
let outerLensY = middle - outerLensSize / 2
|
||||
|
||||
let innerLensSize = outerLensSize * 0.7
|
||||
let innerLensX = center - innerLensSize / 2
|
||||
let innerLensY = middle - innerLensSize / 2
|
||||
|
||||
let outerLensPath = UIBezierPath(ovalInRect: CGRectMake(outerLensX, outerLensY, outerLensSize, outerLensSize))
|
||||
let innerLensPath = UIBezierPath(ovalInRect: CGRectMake(innerLensX, innerLensY, innerLensSize, innerLensSize))
|
||||
|
||||
// Draw flash box
|
||||
|
||||
let flashBoxWidth = cameraWidth * 0.8
|
||||
let flashBoxHeight = cameraHeight * 0.17
|
||||
let flashBoxDeltaWidth = flashBoxWidth * 0.14
|
||||
let flashLeftMostX = cameraX + (cameraWidth - flashBoxWidth) * 0.5
|
||||
let flashBottomMostY = cameraY
|
||||
|
||||
let flashPath = UIBezierPath()
|
||||
flashPath.moveToPoint(CGPointMake(flashLeftMostX, flashBottomMostY))
|
||||
flashPath.addLineToPoint(CGPointMake(flashLeftMostX + flashBoxWidth, flashBottomMostY))
|
||||
flashPath.addLineToPoint(CGPointMake(flashLeftMostX + flashBoxWidth - flashBoxDeltaWidth, flashBottomMostY - flashBoxHeight))
|
||||
flashPath.addLineToPoint(CGPointMake(flashLeftMostX + flashBoxDeltaWidth, flashBottomMostY - flashBoxHeight))
|
||||
flashPath.closePath()
|
||||
flashPath.lineCapStyle = kCGLineCapRound
|
||||
flashPath.lineJoinStyle = kCGLineJoinRound
|
||||
|
||||
// Arrows
|
||||
|
||||
|
||||
let arrowHeadHeigth = cameraHeight * 0.5
|
||||
let arrowHeadWidth = ((width - cameraWidth) / 2) * 0.3
|
||||
let arrowTailHeigth = arrowHeadHeigth * 0.6
|
||||
let arrowTailWidth = ((width - cameraWidth) / 2) * 0.7
|
||||
|
||||
// Draw left arrow
|
||||
|
||||
let arrowLeftX = center - cameraWidth * 0.2
|
||||
let arrowLeftY = middle + cameraHeight * 0.45
|
||||
|
||||
let leftArrowPath = UIBezierPath()
|
||||
leftArrowPath.moveToPoint(CGPointMake(arrowLeftX, arrowLeftY))
|
||||
leftArrowPath.addLineToPoint(CGPointMake(arrowLeftX - arrowHeadWidth, arrowLeftY - arrowHeadHeigth / 2))
|
||||
leftArrowPath.addLineToPoint(CGPointMake(arrowLeftX - arrowHeadWidth, arrowLeftY - arrowTailHeigth / 2))
|
||||
leftArrowPath.addLineToPoint(CGPointMake(arrowLeftX - arrowHeadWidth - arrowTailWidth, arrowLeftY - arrowTailHeigth / 2))
|
||||
leftArrowPath.addLineToPoint(CGPointMake(arrowLeftX - arrowHeadWidth - arrowTailWidth, arrowLeftY + arrowTailHeigth / 2))
|
||||
leftArrowPath.addLineToPoint(CGPointMake(arrowLeftX - arrowHeadWidth, arrowLeftY + arrowTailHeigth / 2))
|
||||
leftArrowPath.addLineToPoint(CGPointMake(arrowLeftX - arrowHeadWidth, arrowLeftY + arrowHeadHeigth / 2))
|
||||
|
||||
// Right arrow
|
||||
|
||||
let arrowRightX = center + cameraWidth * 0.2
|
||||
let arrowRightY = middle + cameraHeight * 0.60
|
||||
|
||||
let rigthArrowPath = UIBezierPath()
|
||||
rigthArrowPath.moveToPoint(CGPointMake(arrowRightX, arrowRightY))
|
||||
rigthArrowPath.addLineToPoint(CGPointMake(arrowRightX + arrowHeadWidth, arrowRightY - arrowHeadHeigth / 2))
|
||||
rigthArrowPath.addLineToPoint(CGPointMake(arrowRightX + arrowHeadWidth, arrowRightY - arrowTailHeigth / 2))
|
||||
rigthArrowPath.addLineToPoint(CGPointMake(arrowRightX + arrowHeadWidth + arrowTailWidth, arrowRightY - arrowTailHeigth / 2))
|
||||
rigthArrowPath.addLineToPoint(CGPointMake(arrowRightX + arrowHeadWidth + arrowTailWidth, arrowRightY + arrowTailHeigth / 2))
|
||||
rigthArrowPath.addLineToPoint(CGPointMake(arrowRightX + arrowHeadWidth, arrowRightY + arrowTailHeigth / 2))
|
||||
rigthArrowPath.addLineToPoint(CGPointMake(arrowRightX + arrowHeadWidth, arrowRightY + arrowHeadHeigth / 2))
|
||||
rigthArrowPath.closePath()
|
||||
|
||||
// Drawing
|
||||
|
||||
paintColor.setFill()
|
||||
rigthArrowPath.fill()
|
||||
strokeColor.setStroke()
|
||||
rigthArrowPath.lineWidth = strokeLineWidth
|
||||
rigthArrowPath.stroke()
|
||||
|
||||
paintColor.setFill()
|
||||
boxPath.fill()
|
||||
strokeColor.setStroke()
|
||||
boxPath.lineWidth = strokeLineWidth
|
||||
boxPath.stroke()
|
||||
|
||||
strokeColor.setFill()
|
||||
outerLensPath.fill()
|
||||
|
||||
paintColor.setFill()
|
||||
innerLensPath.fill()
|
||||
|
||||
paintColor.setFill()
|
||||
flashPath.fill()
|
||||
strokeColor.setStroke()
|
||||
flashPath.lineWidth = strokeLineWidth
|
||||
flashPath.stroke()
|
||||
|
||||
leftArrowPath.closePath()
|
||||
paintColor.setFill()
|
||||
leftArrowPath.fill()
|
||||
strokeColor.setStroke()
|
||||
leftArrowPath.lineWidth = strokeLineWidth
|
||||
leftArrowPath.stroke()
|
||||
}
|
||||
|
||||
// MARK: - UIResponder Methods
|
||||
|
||||
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
|
||||
super.touchesBegan(touches, withEvent: event)
|
||||
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
|
||||
super.touchesMoved(touches, withEvent: event)
|
||||
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
|
||||
super.touchesEnded(touches, withEvent: event)
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
|
||||
super.touchesCancelled(touches, withEvent: event)
|
||||
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue