[REFACTORING] Prepare for switch camera feature

This commit is contained in:
Yannick Loriot 2014-11-13 17:34:40 +01:00
parent 30648714b2
commit 43edacc4ad
6 changed files with 475 additions and 226 deletions

View File

@ -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;

View File

@ -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-->

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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()
}
}