Skip to content

Commit

Permalink
Merge pull request #91 from hyperoslo/feature/camera-position
Browse files Browse the repository at this point in the history
Feature: camera position button
  • Loading branch information
vadymmarkov authored Jan 29, 2018
2 parents 515d0cd + cad9061 commit bdc0450
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 35 deletions.
4 changes: 4 additions & 0 deletions BarcodeScanner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
D50BE3E91C9FE7A80000A34C /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D50BE3E51C9FE7A80000A34C /* [email protected] */; };
D50BE3EA1C9FE7A80000A34C /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D50BE3E61C9FE7A80000A34C /* [email protected] */; };
D50BE3EB1C9FE7A80000A34C /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D50BE3E71C9FE7A80000A34C /* [email protected] */; };
D5349DF8201E42D900CD53EA /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D5349DF7201E42D900CD53EA /* [email protected] */; };
D55281B62016758F00FF3CDD /* HeaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281B52016758F00FF3CDD /* HeaderViewController.swift */; };
D55281B8201675D500FF3CDD /* MessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281B7201675D500FF3CDD /* MessageViewController.swift */; };
D55281BA2016770800FF3CDD /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55281B92016770800FF3CDD /* CameraViewController.swift */; };
Expand All @@ -31,6 +32,7 @@
D50BE3E51C9FE7A80000A34C /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
D50BE3E61C9FE7A80000A34C /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
D50BE3E71C9FE7A80000A34C /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
D5349DF7201E42D900CD53EA /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
D55281B52016758F00FF3CDD /* HeaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderViewController.swift; sourceTree = "<group>"; };
D55281B7201675D500FF3CDD /* MessageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewController.swift; sourceTree = "<group>"; };
D55281B92016770800FF3CDD /* CameraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -60,6 +62,7 @@
D50BE3E31C9FE7A80000A34C /* Images */ = {
isa = PBXGroup;
children = (
D5349DF7201E42D900CD53EA /* [email protected] */,
D50BE3E51C9FE7A80000A34C /* [email protected] */,
D50BE3E61C9FE7A80000A34C /* [email protected] */,
D50BE3E71C9FE7A80000A34C /* [email protected] */,
Expand Down Expand Up @@ -215,6 +218,7 @@
buildActionMask = 2147483647;
files = (
D50BE3E91C9FE7A80000A34C /* [email protected] in Resources */,
D5349DF8201E42D900CD53EA /* [email protected] in Resources */,
D50BE3EB1C9FE7A80000A34C /* [email protected] in Resources */,
D50BE3EA1C9FE7A80000A34C /* [email protected] in Resources */,
);
Expand Down
Binary file added Images/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,16 @@ viewController.messageViewController.textLabel.textColor = .black
**Camera**
```swift
let viewController = BarcodeScannerViewController()
viewController.barCodeFocusViewType = .animated
// Change focus view style
viewController.cameraViewController.barCodeFocusViewType = .animated
// Show camera position button
viewController.cameraViewController.showsCameraButton = true
// Set settings button text
let title = NSAttributedString(
string: "Settings",
attributes: [.font: UIFont.boldSystemFont(ofSize: 17), .foregroundColor : UIColor.white]
)
viewController.settingButton.setAttributedTitle(title, for: UIControlState())
viewController.cameraViewController.settingButton.setAttributedTitle(title, for: UIControlState())
```

**Metadata**
Expand Down
24 changes: 17 additions & 7 deletions Sources/Controllers/BarcodeScannerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import AVFoundation

/// Delegate to handle the captured code.
public protocol BarcodeScannerCodeDelegate: class {
func scanner(_ controller: BarcodeScannerViewController, didCaptureCode code: String, type: String)
func scanner(
_ controller: BarcodeScannerViewController,
didCaptureCode code: String,
type: String
)
}

/// Delegate to report errors.
Expand All @@ -28,6 +32,7 @@ public protocol BarcodeScannerDismissalDelegate: class {
- Not found error message
*/
open class BarcodeScannerViewController: UIViewController {
private static let footerHeight: CGFloat = 75

// MARK: - Public properties

Expand Down Expand Up @@ -66,9 +71,9 @@ open class BarcodeScannerViewController: UIViewController {
public private(set) lazy var cameraViewController: CameraViewController = .init()

// Constraints that are activated when the view is used as a footer.
private lazy var collapsedConstraints: [NSLayoutConstraint] = self.makeCollapsedMessageConstraints()
private lazy var collapsedConstraints: [NSLayoutConstraint] = self.makeCollapsedConstraints()
// Constraints that are activated when the view is used for loading animation and error messages.
private lazy var expandedConstraints: [NSLayoutConstraint] = self.makeExpandedMessageConstraints()
private lazy var expandedConstraints: [NSLayoutConstraint] = self.makeExpandedConstraints()

private var messageView: UIView {
return messageViewController.view
Expand Down Expand Up @@ -220,7 +225,10 @@ private extension BarcodeScannerViewController {
NSLayoutConstraint.activate(
cameraView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
cameraView.bottomAnchor.constraint(
equalTo: view.bottomAnchor,
constant: -BarcodeScannerViewController.footerHeight
)
)

if navigationController != nil {
Expand All @@ -241,7 +249,7 @@ private extension BarcodeScannerViewController {
}
}

private func makeExpandedMessageConstraints() -> [NSLayoutConstraint] {
private func makeExpandedConstraints() -> [NSLayoutConstraint] {
return [
messageView.topAnchor.constraint(equalTo: view.topAnchor),
messageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
Expand All @@ -250,12 +258,14 @@ private extension BarcodeScannerViewController {
]
}

private func makeCollapsedMessageConstraints() -> [NSLayoutConstraint] {
private func makeCollapsedConstraints() -> [NSLayoutConstraint] {
return [
messageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
messageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
messageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
messageView.heightAnchor.constraint(equalToConstant: 75)
messageView.heightAnchor.constraint(
equalToConstant: BarcodeScannerViewController.footerHeight
)
]
}
}
Expand Down
130 changes: 104 additions & 26 deletions Sources/Controllers/CameraViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public final class CameraViewController: UIViewController {

/// Focus view type.
public var barCodeFocusViewType: FocusViewType = .animated
public var showsCameraButton: Bool = false {
didSet {
cameraButton.isHidden = showsCameraButton
}
}
/// `AVCaptureMetadataOutput` metadata object types.
var metadata = [AVMetadataObject.ObjectType]()

Expand All @@ -30,6 +35,8 @@ public final class CameraViewController: UIViewController {
public private(set) lazy var flashButton: UIButton = .init(type: .custom)
/// Button that opens settings to allow camera usage.
public private(set) lazy var settingsButton: UIButton = self.makeSettingsButton()
// Button to switch between front and back camera.
public private(set) lazy var cameraButton: UIButton = self.makeCameraButton()

// Constraints for the focus view when it gets smaller in size.
private var regularFocusViewConstraints = [NSLayoutConstraint]()
Expand All @@ -41,7 +48,7 @@ public final class CameraViewController: UIViewController {
/// Video preview layer.
private var videoPreviewLayer: AVCaptureVideoPreviewLayer?
/// Video capture device. This may be nil when running in Simulator.
private lazy var captureDevice: AVCaptureDevice! = AVCaptureDevice.default(for: .video)
private var captureDevice: AVCaptureDevice?
/// Capture session.
private lazy var captureSession: AVCaptureSession = AVCaptureSession()
// Service used to check authorization status of the capture device
Expand All @@ -62,6 +69,14 @@ public final class CameraViewController: UIViewController {
}
}

private var frontCameraDevice: AVCaptureDevice? {
return AVCaptureDevice.devices(for: .video).first(where: { $0.position == .front })
}

private var backCameraDevice: AVCaptureDevice? {
return AVCaptureDevice.default(for: .video)
}

// MARK: - Initialization

deinit {
Expand All @@ -73,31 +88,21 @@ public final class CameraViewController: UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black

videoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoPreviewLayer?.videoGravity = .resizeAspectFill

guard let videoPreviewLayer = videoPreviewLayer else {
return
}

view.layer.addSublayer(videoPreviewLayer)
view.addSubviews(settingsButton, flashButton, focusView)
view.addSubviews(settingsButton, flashButton, focusView, cameraButton)

torchMode = .off
focusView.isHidden = true
setupCamera()
setupConstraints()

flashButton.addTarget(self, action: #selector(flashButtonDidPress), for: .touchUpInside)
settingsButton.addTarget(self, action: #selector(settingsButtonDidPress), for: .touchUpInside)

NotificationCenter.default.addObserver(
self,
selector: #selector(appWillEnterForeground),
name: NSNotification.Name.UIApplicationWillEnterForeground,
object: nil
)
setupActions()
}

public override func viewDidAppear(_ animated: Bool) {
Expand Down Expand Up @@ -149,19 +154,49 @@ public final class CameraViewController: UIViewController {

// MARK: - Actions

private func setupActions() {
flashButton.addTarget(
self,
action: #selector(handleFlashButtonTap),
for: .touchUpInside
)
settingsButton.addTarget(
self,
action: #selector(handleSettingsButtonTap),
for: .touchUpInside
)
cameraButton.addTarget(
self,
action: #selector(handleCameraButtonTap),
for: .touchUpInside
)

NotificationCenter.default.addObserver(
self,
selector: #selector(appWillEnterForeground),
name: NSNotification.Name.UIApplicationWillEnterForeground,
object: nil
)
}

/// `UIApplicationWillEnterForegroundNotification` action.
@objc private func appWillEnterForeground() {
torchMode = .off
animateFocusView()
}

/// Opens setting to allow camera usage.
@objc private func settingsButtonDidPress() {
@objc private func handleSettingsButtonTap() {
delegate?.cameraViewControllerDidTapSettingsButton(self)
}

/// Swaps camera position.
@objc private func handleCameraButtonTap() {
swapCamera()
}

/// Sets the next torch mode.
@objc private func flashButtonDidPress() {
@objc private func handleFlashButtonTap() {
torchMode = torchMode.next
}

Expand All @@ -179,7 +214,8 @@ public final class CameraViewController: UIViewController {
}

if error == nil {
strongSelf.setupSession()
strongSelf.setupSessionInput(for: .back)
strongSelf.setupSessionOutput()
strongSelf.delegate?.cameraViewControllerDidSetupCaptureSession(strongSelf)
} else {
strongSelf.delegate?.cameraViewControllerDidFailToSetupCaptureSession(strongSelf)
Expand All @@ -188,16 +224,34 @@ public final class CameraViewController: UIViewController {
}

/// Sets up capture input, output and session.
private func setupSession() {
guard let captureDevice = captureDevice, !isSimulatorRunning else {
private func setupSessionInput(for position: AVCaptureDevice.Position) {
guard !isSimulatorRunning else {
return
}

guard let device = position == .front ? frontCameraDevice : backCameraDevice else {
return
}

do {
let input = try AVCaptureDeviceInput(device: captureDevice)
captureSession.addInput(input)
let newInput = try AVCaptureDeviceInput(device: device)
captureDevice = device
// Swap capture device inputs
captureSession.beginConfiguration()
if let currentInput = captureSession.inputs.first as? AVCaptureDeviceInput {
captureSession.removeInput(currentInput)
}
captureSession.addInput(newInput)
captureSession.commitConfiguration()
} catch {
delegate?.cameraViewController(self, didReceiveError: error)
return
}
}

private func setupSessionOutput() {
guard !isSimulatorRunning else {
return
}

let output = AVCaptureMetadataOutput()
Expand All @@ -209,6 +263,14 @@ public final class CameraViewController: UIViewController {
view.setNeedsLayout()
}

/// Switch front/back camera.
private func swapCamera() {
guard let input = captureSession.inputs.first as? AVCaptureDeviceInput else {
return
}
setupSessionInput(for: input.device.position == .back ? .front : .back)
}

// MARK: - Animations

/// Performs focus view animation.
Expand Down Expand Up @@ -248,25 +310,34 @@ private extension CameraViewController {
flashButton.trailingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.trailingAnchor,
constant: -13
),
cameraButton.bottomAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -30
)
)
} else {
NSLayoutConstraint.activate(
flashButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 30),
flashButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -13)
flashButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -13),
cameraButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -30)
)
}

let flashButtonSize: CGFloat = 37
let imageButtonSize: CGFloat = 37

NSLayoutConstraint.activate(
flashButton.widthAnchor.constraint(equalToConstant: flashButtonSize),
flashButton.heightAnchor.constraint(equalToConstant: flashButtonSize),
flashButton.widthAnchor.constraint(equalToConstant: imageButtonSize),
flashButton.heightAnchor.constraint(equalToConstant: imageButtonSize),

settingsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
settingsButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
settingsButton.widthAnchor.constraint(equalToConstant: 150),
settingsButton.heightAnchor.constraint(equalToConstant: 50)
settingsButton.heightAnchor.constraint(equalToConstant: 50),

cameraButton.widthAnchor.constraint(equalToConstant: 48),
cameraButton.heightAnchor.constraint(equalToConstant: 48),
cameraButton.trailingAnchor.constraint(equalTo: flashButton.trailingAnchor)
)

setupFocusViewConstraints()
Expand Down Expand Up @@ -345,6 +416,13 @@ private extension CameraViewController {
button.sizeToFit()
return button
}

func makeCameraButton() -> UIButton {
let button = UIButton(type: .custom)
button.setImage(imageNamed("cameraRotate"), for: UIControlState())
button.isHidden = showsCameraButton
return button
}
}

// MARK: - AVCaptureMetadataOutputObjectsDelegate
Expand Down

0 comments on commit bdc0450

Please sign in to comment.