From 6674f8bc1bf34657e2e1eba7ead9c6a1cb9aba5f Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Thu, 25 Jan 2018 20:59:36 +0100 Subject: [PATCH 1/3] Update documentation --- .../BarcodeScannerViewController.swift | 91 +++++++------ .../Controllers/CameraViewController.swift | 40 +++--- .../Controllers/HeaderViewController.swift | 10 +- .../Controllers/MessageViewController.swift | 127 ++++++++++-------- Sources/DataStructures/FocusViewType.swift | 1 + Sources/DataStructures/State.swift | 43 +++--- Sources/DataStructures/TorchMode.swift | 31 ++--- .../AVMetadataObject+Extensions.swift | 4 +- .../NSLayoutConstraint+Extensions.swift | 1 + Sources/Extensions/UIView+Extensions.swift | 1 + .../UIViewController+Extensions.swift | 7 +- Sources/Helpers/Functions.swift | 18 ++- Sources/Helpers/VideoPermissionService.swift | 7 +- 13 files changed, 193 insertions(+), 188 deletions(-) diff --git a/Sources/Controllers/BarcodeScannerViewController.swift b/Sources/Controllers/BarcodeScannerViewController.swift index a832284..5fdb820 100644 --- a/Sources/Controllers/BarcodeScannerViewController.swift +++ b/Sources/Controllers/BarcodeScannerViewController.swift @@ -31,15 +31,17 @@ open class BarcodeScannerViewController: UIViewController { // MARK: - Public properties - /// When the flag is set to `true` controller returns a captured code - /// and waits for the next reset action. - public var isOneTimeSearch = true /// Delegate to handle the captured code. public weak var codeDelegate: BarcodeScannerCodeDelegate? /// Delegate to report errors. public weak var errorDelegate: BarcodeScannerErrorDelegate? /// Delegate to dismiss barcode scanner when the close button has been pressed. public weak var dismissalDelegate: BarcodeScannerDismissalDelegate? + + /// When the flag is set to `true` controller returns a captured code + /// and waits for the next reset action. + public var isOneTimeSearch = true + /// `AVCaptureMetadataOutput` metadata object types. public var metadata = AVMetadataObject.ObjectType.barcodeScannerMetadata { didSet { @@ -51,13 +53,15 @@ open class BarcodeScannerViewController: UIViewController { /// Flag to lock session from capturing. private var locked = false + /// Flag to check if layout constraints has been activated. + private var constraintsActivated = false // MARK: - UI /// Information view with description label. - private lazy var messageViewController: MessageViewController = .init() + public private(set) lazy var messageViewController: MessageViewController = .init() /// Camera view with custom buttons. - private lazy var cameraViewController: CameraViewController = .init() + public private(set) lazy var cameraViewController: CameraViewController = .init() private var messageView: UIView { return messageViewController.view @@ -93,32 +97,7 @@ open class BarcodeScannerViewController: UIViewController { open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - - let cameraView = cameraViewController.view! - - NSLayoutConstraint.activate( - cameraView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ) - - if navigationController != nil { - cameraView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true - } else { - let headerViewController = HeaderViewController() - headerViewController.delegate = self - add(childViewController: headerViewController) - - let headerView = headerViewController.view! - - NSLayoutConstraint.activate( - headerView.topAnchor.constraint(equalTo: view.topAnchor), - headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - headerView.bottomAnchor.constraint(equalTo: headerViewController.navigationBar.bottomAnchor), - cameraView.topAnchor.constraint(equalTo: headerView.bottomAnchor) - ) - } + setupConstraints() } open override func viewWillTransition(to size: CGSize, @@ -133,7 +112,6 @@ open class BarcodeScannerViewController: UIViewController { /** Shows error message and goes back to the scanning mode. - - Parameter errorMessage: Error message that overrides the message from the config. */ public func resetWithError(message: String? = nil) { @@ -142,7 +120,6 @@ open class BarcodeScannerViewController: UIViewController { /** Resets the controller to the scanning mode. - - Parameter animated: Flag to show scanner with or without animation. */ public func reset(animated: Bool = true) { @@ -158,7 +135,8 @@ open class BarcodeScannerViewController: UIViewController { return } - let animatedTransition = newValue.state == .processing || oldValue.state == .processing + let animatedTransition = newValue.state == .processing + || oldValue.state == .processing || oldValue.state == .notFound let duration = newValue.animated && animatedTransition ? 0.5 : 0.0 let delayReset = oldValue.state == .processing || oldValue.state == .notFound @@ -187,9 +165,7 @@ open class BarcodeScannerViewController: UIViewController { })) } - /** - Resets the current state. - */ + /// Resets the current state. private func resetState() { locked = status.state == .processing && isOneTimeSearch if status.state == .scanning { @@ -203,7 +179,6 @@ open class BarcodeScannerViewController: UIViewController { /** Simulates flash animation. - - Parameter processing: Flag to set the current state to `.processing`. */ private func animateFlash(whenProcessing: Bool = false) { @@ -229,16 +204,52 @@ open class BarcodeScannerViewController: UIViewController { } } +// MARK: - Layout + +private extension BarcodeScannerViewController { + private func setupConstraints() { + guard constraintsActivated else { + return + } + + let cameraView = cameraViewController.view! + + NSLayoutConstraint.activate( + cameraView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ) + + if navigationController != nil { + cameraView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + } else { + let headerViewController = HeaderViewController() + headerViewController.delegate = self + add(childViewController: headerViewController) + + let headerView = headerViewController.view! + + NSLayoutConstraint.activate( + headerView.topAnchor.constraint(equalTo: view.topAnchor), + headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + headerView.bottomAnchor.constraint(equalTo: headerViewController.navigationBar.bottomAnchor), + cameraView.topAnchor.constraint(equalTo: headerView.bottomAnchor) + ) + } + } +} + // MARK: - HeaderViewControllerDelegate extension BarcodeScannerViewController: HeaderViewControllerDelegate { - public func headerViewControllerDidTapCloseButton(_ controller: HeaderViewController) { + func headerViewControllerDidTapCloseButton(_ controller: HeaderViewController) { status = Status(state: .scanning) dismissalDelegate?.scannerDidDismiss(self) } } -// MARK: - AVCaptureMetadataOutputObjectsDelegate +// MARK: - CameraViewControllerDelegate extension BarcodeScannerViewController: CameraViewControllerDelegate { func cameraViewControllerDidSetupCaptureSession(_ controller: CameraViewController) { diff --git a/Sources/Controllers/CameraViewController.swift b/Sources/Controllers/CameraViewController.swift index 8cf3e06..a71fce3 100644 --- a/Sources/Controllers/CameraViewController.swift +++ b/Sources/Controllers/CameraViewController.swift @@ -1,6 +1,7 @@ import UIKit import AVFoundation +/// Delegate to handle camera setup and video capturing. protocol CameraViewControllerDelegate: class { func cameraViewControllerDidSetupCaptureSession(_ controller: CameraViewController) func cameraViewControllerDidFailToSetupCaptureSession(_ controller: CameraViewController) @@ -12,21 +13,24 @@ protocol CameraViewControllerDelegate: class { ) } -final class CameraViewController: UIViewController { +/// View controller responsible for camera controls and video capturing. +public final class CameraViewController: UIViewController { weak var delegate: CameraViewControllerDelegate? + /// Focus view type. - var barCodeFocusViewType: FocusViewType = .animated + public var barCodeFocusViewType: FocusViewType = .animated /// `AVCaptureMetadataOutput` metadata object types. var metadata = [AVMetadataObject.ObjectType]() - // MARK: - UI + // MARK: - UI proterties /// Animated focus view. - private lazy var focusView: UIView = self.makeFocusView() + public private(set) lazy var focusView: UIView = self.makeFocusView() /// Button to change torch mode. - public lazy var flashButton: UIButton = .init(type: .custom) + public private(set) lazy var flashButton: UIButton = .init(type: .custom) /// Button that opens settings to allow camera usage. - private lazy var settingsButton: UIButton = self.makeSettingsButton() + public private(set) lazy var settingsButton: UIButton = self.makeSettingsButton() + // Constraints for the focus view when it gets smaller in size. private var regularFocusViewConstraints = [NSLayoutConstraint]() // Constraints for the focus view when it gets bigger in size. @@ -66,7 +70,7 @@ final class CameraViewController: UIViewController { // MARK: - View lifecycle - override func viewDidLoad() { + public override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .black @@ -89,24 +93,26 @@ final class CameraViewController: UIViewController { settingsButton.addTarget(self, action: #selector(settingsButtonDidPress), for: .touchUpInside) NotificationCenter.default.addObserver( - self, selector: #selector(appWillEnterForeground), + self, + selector: #selector(appWillEnterForeground), name: NSNotification.Name.UIApplicationWillEnterForeground, - object: nil) + object: nil + ) } - override func viewDidAppear(_ animated: Bool) { + public override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) setupVideoPreviewLayerOrientation() animateFocusView() } - override func viewWillDisappear(_ animated: Bool) { + public override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) captureSession.stopRunning() } - override func viewWillTransition(to size: CGSize, - with coordinator: UIViewControllerTransitionCoordinator) { + public override func viewWillTransition(to size: CGSize, + with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { [weak self] _ in self?.setupVideoPreviewLayerOrientation() @@ -115,7 +121,7 @@ final class CameraViewController: UIViewController { } } - // MARK: - State handling + // MARK: - Video capturing func startCapturing() { guard !isSimulatorRunning else { @@ -341,9 +347,9 @@ private extension CameraViewController { // MARK: - AVCaptureMetadataOutputObjectsDelegate extension CameraViewController: AVCaptureMetadataOutputObjectsDelegate { - func metadataOutput(_ output: AVCaptureMetadataOutput, - didOutput metadataObjects: [AVMetadataObject], - from connection: AVCaptureConnection) { + public func metadataOutput(_ output: AVCaptureMetadataOutput, + didOutput metadataObjects: [AVMetadataObject], + from connection: AVCaptureConnection) { delegate?.cameraViewController(self, didOutput: metadataObjects) } } diff --git a/Sources/Controllers/HeaderViewController.swift b/Sources/Controllers/HeaderViewController.swift index c9bc329..de671ba 100644 --- a/Sources/Controllers/HeaderViewController.swift +++ b/Sources/Controllers/HeaderViewController.swift @@ -1,18 +1,20 @@ import UIKit /// Delegate to handle touch event of the close button. -public protocol HeaderViewControllerDelegate: class { +protocol HeaderViewControllerDelegate: class { func headerViewControllerDidTapCloseButton(_ controller: HeaderViewController) } -/// Controller with title label and close button. +/// View controller with title label and close button. /// It will be added as a child view controller if `BarcodeScannerController` is being presented. public final class HeaderViewController: UIViewController { - public weak var delegate: HeaderViewControllerDelegate? + weak var delegate: HeaderViewControllerDelegate? + + // MARK: - UI properties /// Header view with title label and close button. public private(set) lazy var navigationBar: UINavigationBar = self.makeNavigationBar() - /// Title view of the navigation bar + /// Title view of the navigation bar. public private(set) lazy var titleLabel: UILabel = self.makeTitleLabel() /// Left bar button item of the navigation bar. public private(set) lazy var closeButton: UIButton = self.makeCloseButton() diff --git a/Sources/Controllers/MessageViewController.swift b/Sources/Controllers/MessageViewController.swift index 58a1559..24435d3 100644 --- a/Sources/Controllers/MessageViewController.swift +++ b/Sources/Controllers/MessageViewController.swift @@ -1,8 +1,14 @@ import UIKit +/// View controller used for showing info text and loading animation. public final class MessageViewController: UIViewController { - // Blur effect view. - private lazy var blurView: UIVisualEffectView = .init(effect: UIBlurEffect(style: .extraLight)) + // Image tint color for all the states, except for `.notFound`. + public var regularTintColor: UIColor = .black + // Image tint color for `.notFound` state. + public var errorTintColor: UIColor = .red + + // MARK: - UI properties + /// Text label. public private(set) lazy var textLabel: UILabel = .init() /// Info image view. @@ -10,12 +16,13 @@ public final class MessageViewController: UIViewController { /// Border view. public private(set) lazy var borderView: UIView = .init() + /// Blur effect view. + private lazy var blurView: UIVisualEffectView = .init(effect: UIBlurEffect(style: .extraLight)) + // Constraints that are activated when the view is used as a footer. 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.makeExpandedConstraints() - public var regularTintColor: UIColor = .black - public var errorTintColor: UIColor = .red - var state: State = .scanning { didSet { handleStateUpdate() @@ -37,62 +44,9 @@ public final class MessageViewController: UIViewController { blurView.frame = view.bounds } - // MARK: - Subviews - - private func setupSubviews() { - textLabel.translatesAutoresizingMaskIntoConstraints = false - textLabel.textColor = .black - textLabel.numberOfLines = 3 - - imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.image = imageNamed("info").withRenderingMode(.alwaysTemplate) - imageView.tintColor = .black - - borderView.translatesAutoresizingMaskIntoConstraints = false - borderView.backgroundColor = .clear - borderView.layer.borderWidth = 2 - borderView.layer.cornerRadius = 10 - borderView.layer.borderColor = UIColor.black.cgColor - } - - private func handleStateUpdate() { - borderView.isHidden = true - borderView.layer.removeAllAnimations() - textLabel.text = state.text - - switch state { - case .scanning, .unauthorized: - textLabel.font = UIFont.boldSystemFont(ofSize: 14) - textLabel.numberOfLines = 3 - textLabel.textAlignment = .left - imageView.tintColor = regularTintColor - case .processing: - textLabel.font = UIFont.boldSystemFont(ofSize: 16) - textLabel.numberOfLines = 10 - textLabel.textAlignment = .center - borderView.isHidden = false - imageView.tintColor = regularTintColor - case .notFound: - textLabel.font = UIFont.boldSystemFont(ofSize: 16) - textLabel.numberOfLines = 10 - textLabel.textAlignment = .center - imageView.tintColor = errorTintColor - } - - if state == .scanning || state == .unauthorized { - expandedConstraints.forEach({ $0.isActive = false }) - collapsedConstraints.forEach({ $0.isActive = true }) - } else { - collapsedConstraints.forEach({ $0.isActive = false }) - expandedConstraints.forEach({ $0.isActive = true }) - } - } - // MARK: - Animations - /** - Animates blur and border view. - */ + /// Animates blur and border view. func animateLoading() { animate(blurStyle: .light) animate(borderViewAngle: CGFloat(Double.pi/2)) @@ -142,8 +96,63 @@ public final class MessageViewController: UIViewController { self?.animate(borderViewAngle: borderViewAngle + CGFloat(Double.pi / 2)) })) } + + // MARK: - Subviews + + private func setupSubviews() { + textLabel.translatesAutoresizingMaskIntoConstraints = false + textLabel.textColor = .black + textLabel.numberOfLines = 3 + + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.image = imageNamed("info").withRenderingMode(.alwaysTemplate) + imageView.tintColor = .black + + borderView.translatesAutoresizingMaskIntoConstraints = false + borderView.backgroundColor = .clear + borderView.layer.borderWidth = 2 + borderView.layer.cornerRadius = 10 + borderView.layer.borderColor = UIColor.black.cgColor + } + + // MARK: - State handling + + private func handleStateUpdate() { + borderView.isHidden = true + borderView.layer.removeAllAnimations() + textLabel.text = state.text + + switch state { + case .scanning, .unauthorized: + textLabel.font = UIFont.boldSystemFont(ofSize: 14) + textLabel.numberOfLines = 3 + textLabel.textAlignment = .left + imageView.tintColor = regularTintColor + case .processing: + textLabel.font = UIFont.boldSystemFont(ofSize: 16) + textLabel.numberOfLines = 10 + textLabel.textAlignment = .center + borderView.isHidden = false + imageView.tintColor = regularTintColor + case .notFound: + textLabel.font = UIFont.boldSystemFont(ofSize: 16) + textLabel.numberOfLines = 10 + textLabel.textAlignment = .center + imageView.tintColor = errorTintColor + } + + if state == .scanning || state == .unauthorized { + expandedConstraints.forEach({ $0.isActive = false }) + collapsedConstraints.forEach({ $0.isActive = true }) + } else { + collapsedConstraints.forEach({ $0.isActive = false }) + expandedConstraints.forEach({ $0.isActive = true }) + } + } } +// MARK: - Layout + extension MessageViewController { private func makeExpandedConstraints() -> [NSLayoutConstraint] { let padding: CGFloat = 10 diff --git a/Sources/DataStructures/FocusViewType.swift b/Sources/DataStructures/FocusViewType.swift index 123bbce..5baa26f 100644 --- a/Sources/DataStructures/FocusViewType.swift +++ b/Sources/DataStructures/FocusViewType.swift @@ -1,5 +1,6 @@ import UIKit +/// Type of the focus view public enum FocusViewType { case animated case oneDimension diff --git a/Sources/DataStructures/State.swift b/Sources/DataStructures/State.swift index 4597022..1d6c68f 100644 --- a/Sources/DataStructures/State.swift +++ b/Sources/DataStructures/State.swift @@ -2,26 +2,20 @@ import UIKit // MARK: - Status -/** - Status is a holder of the current state - with a few additional configuration properties. - */ +/// Status is a holder of the current state with a few additional configuration properties. struct Status { /// The current state. let state: State - - /// A flag to enable/disable animation. + /// Flag to enable/disable animation. let animated: Bool - - /// A text that overrides a text from the state. + /// Text that overrides a text from the state. let text: String /** Creates a new instance of `Status`. - - - Parameter state: A state. - - Parameter animated: A flag to enable/disable animation. - - Parameter text: A text that overrides a text from the state. + - Parameter state: State value. + - Parameter animated: Flag to enable/disable animation. + - Parameter text: Text that overrides a text from the state. */ init(state: State, animated: Bool = true, text: String? = nil) { self.state = state @@ -30,31 +24,26 @@ struct Status { } } -// MARK: - State. +// MARK: - State -/** - Barcode scanner state. - */ +/// Barcode scanner state. enum State { - case scanning, processing, unauthorized, notFound - - typealias Styles = (tint: UIColor, font: UIFont, alignment: NSTextAlignment) + case scanning + case processing + case unauthorized + case notFound /// State message. var text: String { - let string: String - switch self { case .scanning: - string = localizedString("INFO_DESCRIPTION_TEXT") + return localizedString("INFO_DESCRIPTION_TEXT") case .processing: - string = localizedString("INFO_LOADING_TITLE") + return localizedString("INFO_LOADING_TITLE") case .unauthorized: - string = localizedString("ASK_FOR_PERMISSION_TEXT") + return localizedString("ASK_FOR_PERMISSION_TEXT") case .notFound: - string = localizedString("NO_PRODUCT_ERROR_TITLE") + return localizedString("NO_PRODUCT_ERROR_TITLE") } - - return string } } diff --git a/Sources/DataStructures/TorchMode.swift b/Sources/DataStructures/TorchMode.swift index 659482a..86f8597 100644 --- a/Sources/DataStructures/TorchMode.swift +++ b/Sources/DataStructures/TorchMode.swift @@ -1,51 +1,38 @@ import UIKit import AVFoundation -/** - Wrapper around `AVCaptureTorchMode`. - */ +/// Wrapper around `AVCaptureTorchMode`. public enum TorchMode { - case on, off + case on + case off /// Returns the next torch mode. var next: TorchMode { - let result: TorchMode - switch self { case .on: - result = .off + return .off case .off: - result = .on + return .on } - - return result } /// Torch mode image. var image: UIImage { - let result: UIImage - switch self { case .on: - result = imageNamed("flashOn") + return imageNamed("flashOn") case .off: - result = imageNamed("flashOff") + return imageNamed("flashOff") } - - return result } /// Returns `AVCaptureTorchMode` value. var captureTorchMode: AVCaptureDevice.TorchMode { - let result: AVCaptureDevice.TorchMode - switch self { case .on: - result = .on + return .on case .off: - result = .off + return .off } - - return result } } diff --git a/Sources/Extensions/AVMetadataObject+Extensions.swift b/Sources/Extensions/AVMetadataObject+Extensions.swift index 9718c4c..3f72281 100644 --- a/Sources/Extensions/AVMetadataObject+Extensions.swift +++ b/Sources/Extensions/AVMetadataObject+Extensions.swift @@ -3,9 +3,7 @@ import AVFoundation extension AVMetadataObject.ObjectType { public static let upca: AVMetadataObject.ObjectType = .init(rawValue: "org.gs1.UPC-A") - /** - `AVCaptureMetadataOutput` metadata object types. - */ + /// `AVCaptureMetadataOutput` metadata object types. public static var barcodeScannerMetadata = [ AVMetadataObject.ObjectType.aztec, AVMetadataObject.ObjectType.code128, diff --git a/Sources/Extensions/NSLayoutConstraint+Extensions.swift b/Sources/Extensions/NSLayoutConstraint+Extensions.swift index f9bfc83..0def830 100644 --- a/Sources/Extensions/NSLayoutConstraint+Extensions.swift +++ b/Sources/Extensions/NSLayoutConstraint+Extensions.swift @@ -1,6 +1,7 @@ import UIKit extension NSLayoutConstraint { + /// A helper function to activate layout constraints. static func activate(_ constraints: NSLayoutConstraint? ...) { for case let constraint in constraints { guard let constraint = constraint else { diff --git a/Sources/Extensions/UIView+Extensions.swift b/Sources/Extensions/UIView+Extensions.swift index 6173b93..9671814 100644 --- a/Sources/Extensions/UIView+Extensions.swift +++ b/Sources/Extensions/UIView+Extensions.swift @@ -1,6 +1,7 @@ import UIKit extension UIView { + /// A helper function to add multiple subviews. func addSubviews(_ subviews: UIView...) { subviews.forEach { addSubview($0) diff --git a/Sources/Extensions/UIViewController+Extensions.swift b/Sources/Extensions/UIViewController+Extensions.swift index 8744db5..674978a 100644 --- a/Sources/Extensions/UIViewController+Extensions.swift +++ b/Sources/Extensions/UIViewController+Extensions.swift @@ -1,16 +1,11 @@ import UIKit extension UIViewController { + /// A helper function to add child view controller. func add(childViewController: UIViewController) { childViewController.willMove(toParentViewController: self) addChildViewController(childViewController) view.addSubview(childViewController.view) childViewController.didMove(toParentViewController: self) } - - func remove(childViewController: UIViewController) { - childViewController.willMove(toParentViewController: nil) - childViewController.view.removeFromSuperview() - childViewController.removeFromParentViewController() - } } diff --git a/Sources/Helpers/Functions.swift b/Sources/Helpers/Functions.swift index 3cdd646..2514b2b 100644 --- a/Sources/Helpers/Functions.swift +++ b/Sources/Helpers/Functions.swift @@ -3,7 +3,6 @@ import AVFoundation /** Returns image with a given name from the resource bundle. - - Parameter name: Image name. - Returns: An image. */ @@ -12,18 +11,22 @@ func imageNamed(_ name: String) -> UIImage { var bundle = Bundle(for: cls) let traitCollection = UITraitCollection(displayScale: 3) - if let path = bundle.resourcePath, - let resourceBundle = Bundle(path: path + "/BarcodeScanner.bundle") { - bundle = resourceBundle + if let resourceBundle = bundle.resourcePath.flatMap({ Bundle(path: $0 + "/BarcodeScanner.bundle") }) { + bundle = resourceBundle } - guard let image = UIImage(named: name, in: bundle, - compatibleWith: traitCollection) - else { return UIImage() } + guard let image = UIImage(named: name, in: bundle, compatibleWith: traitCollection) else { + return UIImage() + } return image } +/** + Returns localized string using localization resource bundle. + - Parameter name: Image name. + - Returns: An image. + */ func localizedString(_ key: String) -> String { if let path = Bundle(for: BarcodeScannerViewController.self).resourcePath, let resourceBundle = Bundle(path: path + "/Localization.bundle") { @@ -32,6 +35,7 @@ func localizedString(_ key: String) -> String { return key } +/// Checks if the app is running in Simulator. var isSimulatorRunning: Bool = { #if (arch(i386) || arch(x86_64)) && os(iOS) return true diff --git a/Sources/Helpers/VideoPermissionService.swift b/Sources/Helpers/VideoPermissionService.swift index 0b32fe6..d2785c8 100644 --- a/Sources/Helpers/VideoPermissionService.swift +++ b/Sources/Helpers/VideoPermissionService.swift @@ -1,13 +1,14 @@ import AVFoundation -/// Service used to check authorization status of the capture device +/// Service used to check authorization status of the capture device. final class VideoPermissionService { enum Error: Swift.Error { case notAuthorizedToUseCamera } // MARK: - Authorization - /// Check authorization status of the capture device + + /// Checks authorization status of the capture device. func checkPersmission(completion: @escaping (Error?) -> Void) { switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: @@ -19,7 +20,7 @@ final class VideoPermissionService { } } - /// Ask for permission to use video + /// Asks for permission to use video. private func askForPermissions(_ completion: @escaping (Error?) -> Void) { AVCaptureDevice.requestAccess(for: .video) { granted in DispatchQueue.main.async { From 21d706f1f529f329661044a5cbbc852328e3c036 Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Thu, 25 Jan 2018 21:28:15 +0100 Subject: [PATCH 2/3] Update readme --- README.md | 142 +++++++++--------- .../BarcodeScannerViewController.swift | 3 +- 2 files changed, 77 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index ea58c5f..44e3e04 100644 --- a/README.md +++ b/README.md @@ -39,29 +39,29 @@ barcode capturing functionality and a great user experience. ### Controller -To start capturing just instantiate `BarcodeScannerController`, set needed +To start capturing just instantiate `BarcodeScannerViewController`, set needed delegates and present it: ```swift -let controller = BarcodeScannerController() -controller.codeDelegate = self -controller.errorDelegate = self -controller.dismissalDelegate = self +let viewController = BarcodeScannerViewController() +viewController.codeDelegate = self +viewController.errorDelegate = self +viewController.dismissalDelegate = self -present(controller, animated: true, completion: nil) +present(viewController, animated: true, completion: nil) ```
BarcodeScanner scanning

-You can also push `BarcodeScannerController` to your navigation stack: +You can also push `BarcodeScannerViewController` to your navigation stack: ```swift -let controller = BarcodeScannerController() -controller.codeDelegate = self +let viewController = BarcodeScannerViewController() +viewController.codeDelegate = self -navigationController?.pushViewController(controller, animated: true) +navigationController?.pushViewController(viewController, animated: true) ``` ### Delegates @@ -72,8 +72,7 @@ Use `BarcodeScannerCodeDelegate` when you want to get the captured code back. ```swift extension ViewController: BarcodeScannerCodeDelegate { - - func barcodeScanner(_ controller: BarcodeScannerController, didCaptureCode code: String, type: String) { + func barcodeScanner(_ controller: BarcodeScannerViewController, didCaptureCode code: String, type: String) { print(code) controller.reset() } @@ -85,8 +84,7 @@ extension ViewController: BarcodeScannerCodeDelegate { Use `BarcodeScannerErrorDelegate` when you want to handle session errors. ```swift extension ViewController: BarcodeScannerErrorDelegate { - - func barcodeScanner(_ controller: BarcodeScannerController, didReceiveError error: Error) { + func barcodeScanner(_ controller: BarcodeScannerViewController, didReceiveError error: Error) { print(error) } } @@ -95,13 +93,12 @@ extension ViewController: BarcodeScannerErrorDelegate { **Dismissal delegate** Use `BarcodeScannerDismissalDelegate` to handle "Close button" tap. -**Please note** that `BarcodeScannerController` doesn't dismiss itself if it was -presented initially. +**Please note** that `BarcodeScannerViewController` doesn't dismiss itself if +it was presented initially. ```swift extension ViewController: BarcodeScannerDismissalDelegate { - - func barcodeScannerDidDismiss(_ controller: BarcodeScannerController) { + func barcodeScannerDidDismiss(_ controller: BarcodeScannerViewController) { controller.dismiss(animated: true, completion: nil) } } @@ -109,83 +106,94 @@ extension ViewController: BarcodeScannerDismissalDelegate { ### Actions -When the code is captured `BarcodeScannerController` switches to the processing +When the code is captured `BarcodeScannerViewController` switches to the processing mode:
BarcodeScanner loading

-While the user see a nice loading animation you can perform some +While the user sees a nice loading animation you can perform some background task, for example make a network request to fetch product info based on the code. When the task is done you have 3 options to proceed: -1. Dismiss `BarcodeScannerController` and show your results. +1. Dismiss `BarcodeScannerViewController` and show your results. + +```swift +func barcodeScanner(_ controller: BarcodeScannerViewController, didCaptureCode code: String, type: String) { + // Code processing + controller.dismiss(animated: true, completion: nil) +} +``` - ```swift - func barcodeScanner(_ controller: BarcodeScannerController, didCaptureCode code: String, type: String) { - // Code processing - controller.dismiss(animated: true, completion: nil) - } - ``` 2. Show an error message and switch back to the scanning mode (for example, when there is no product found with a given barcode in your database): -
- BarcodeScanner error -

+
+BarcodeScanner error +

+ +```swift +func barcodeScanner(_ controller: BarcodeScannerViewController, didCaptureCode code: String, type: String) { + // Code processing + controller.resetWithError(message: "Error message") + // If message is not provided the default message will be used instead. +} +``` - ```swift - func barcodeScanner(_ controller: BarcodeScannerController, didCaptureCode code: String, type: String) { - // Code processing - controller.resetWithError(message: "Error message") - // If message is not provided the default message from the config will be used instead. - } - ``` 3. Reset the controller to the scanning mode (with or without animation): ```swift - func barcodeScanner(_ controller: BarcodeScannerController, didCaptureCode code: String, type: String) { + func barcodeScanner(_ controller: BarcodeScannerViewController, didCaptureCode code: String, type: String) { // Code processing controller.reset(animated: true) } ``` If you want to do continuous barcode scanning just set the `isOneTimeSearch` -property on your `BarcodeScannerController` instance to `false`. +property on your `BarcodeScannerViewController` instance to `false`. ### Customization -We styled **BarcodeScanner** to make it look nice, but feel free to customize -its appearance by changing global configuration variables: +We styled **BarcodeScanner** to make it look nice, but you can always use public +properties or inheritance to customize its appearance. + +**Header** + +```swift +let viewController = BarcodeScannerViewController() +viewController.headerViewController.titleLabel.text = "Scan barcode" +viewController.headerViewController.closeButton.tintColor = .red +``` + +**Please note** that `HeaderViewController` is visible only when +`BarcodeScannerViewController` is being presented. + +**Footer and messages** + +```swift +let viewController = BarcodeScannerViewController() +viewController.messageViewController.regularTintColor = .black +viewController.messageViewController.errorTintColor = .red +viewController.messageViewController.textLabel.textColor = .black +``` + +**Camera** +```swift +let viewController = BarcodeScannerViewController() +viewController.barCodeFocusViewType = .animated +let title = NSAttributedString( + string: "Settings", + attributes: [.font: UIFont.boldSystemFont(ofSize: 17), .foregroundColor : UIColor.white] +) +viewController.settingButton.setAttributedTitle(title, for: UIControlState()) +``` +**Metadata** ```swift -// Strings -BarcodeScanner.Title.text = NSLocalizedString("Scan barcode", comment: "") -BarcodeScanner.CloseButton.text = NSLocalizedString("Close", comment: "") -BarcodeScanner.SettingsButton.text = NSLocalizedString("Settings", comment: "") -BarcodeScanner.Info.text = NSLocalizedString( - "Place the barcode within the window to scan. The search will start automatically.", comment: "") -BarcodeScanner.Info.loadingText = NSLocalizedString("Looking for your product...", comment: "") -BarcodeScanner.Info.notFoundText = NSLocalizedString("No product found.", comment: "") -BarcodeScanner.Info.settingsText = NSLocalizedString( - "In order to scan barcodes you have to allow camera under your settings.", comment: "") - -// Fonts -BarcodeScanner.Title.font = UIFont.boldSystemFont(ofSize: 17) -BarcodeScanner.CloseButton.font = UIFont.boldSystemFont(ofSize: 17) -BarcodeScanner.SettingsButton.font = UIFont.boldSystemFont(ofSize: 17) -BarcodeScanner.Info.font = UIFont.boldSystemFont(ofSize: 14) -BarcodeScanner.Info.loadingFont = UIFont.boldSystemFont(ofSize: 16) - -// Colors -BarcodeScanner.Title.color = UIColor.black -BarcodeScanner.CloseButton.color = UIColor.black -BarcodeScanner.SettingsButton.color = UIColor.white -BarcodeScanner.Info.textColor = UIColor.black -BarcodeScanner.Info.tint = UIColor.black -BarcodeScanner.Info.loadingTint = UIColor.black -BarcodeScanner.Info.notFoundTint = UIColor.red +// Add extra metadata object type +let viewController = BarcodeScannerViewController() +viewController.metadata.append(AVMetadataObject.ObjectType.qr) ``` ## Installation diff --git a/Sources/Controllers/BarcodeScannerViewController.swift b/Sources/Controllers/BarcodeScannerViewController.swift index 5fdb820..28c608b 100644 --- a/Sources/Controllers/BarcodeScannerViewController.swift +++ b/Sources/Controllers/BarcodeScannerViewController.swift @@ -58,6 +58,8 @@ open class BarcodeScannerViewController: UIViewController { // MARK: - UI + // Title label and close button. + public private(set) lazy var headerViewController: HeaderViewController = .init() /// Information view with description label. public private(set) lazy var messageViewController: MessageViewController = .init() /// Camera view with custom buttons. @@ -223,7 +225,6 @@ private extension BarcodeScannerViewController { if navigationController != nil { cameraView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true } else { - let headerViewController = HeaderViewController() headerViewController.delegate = self add(childViewController: headerViewController) From 1b4217c40f84cd54870689302727cc2917611ea7 Mon Sep 17 00:00:00 2001 From: Vadym Markov Date: Thu, 25 Jan 2018 21:55:03 +0100 Subject: [PATCH 3/3] Update example --- .../BarcodeScannerExample/ViewController.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Example/BarcodeScannerExample/BarcodeScannerExample/ViewController.swift b/Example/BarcodeScannerExample/BarcodeScannerExample/ViewController.swift index 5af7de6..0fcb12f 100644 --- a/Example/BarcodeScannerExample/BarcodeScannerExample/ViewController.swift +++ b/Example/BarcodeScannerExample/BarcodeScannerExample/ViewController.swift @@ -6,15 +6,15 @@ final class ViewController: UIViewController { @IBOutlet var pushScannerButton: UIButton! @IBAction func handleScannerPresent(_ sender: Any, forEvent event: UIEvent) { - let controller = makeBarcodeScannerViewController() - controller.title = "Barcode Scanner" - present(controller, animated: true, completion: nil) + let viewController = makeBarcodeScannerViewController() + viewController.title = "Barcode Scanner" + present(viewController, animated: true, completion: nil) } @IBAction func handleScannerPush(_ sender: Any, forEvent event: UIEvent) { - let controller = makeBarcodeScannerViewController() - controller.title = "Barcode Scanner" - navigationController?.pushViewController(controller, animated: true) + let viewController = makeBarcodeScannerViewController() + viewController.title = "Barcode Scanner" + navigationController?.pushViewController(viewController, animated: true) } private func makeBarcodeScannerViewController() -> BarcodeScannerViewController { @@ -33,8 +33,7 @@ extension ViewController: BarcodeScannerCodeDelegate { print("Barcode Data: \(code)") print("Symbology Type: \(type)") - let delayTime = DispatchTime.now() + Double(Int64(6 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) - DispatchQueue.main.asyncAfter(deadline: delayTime) { + DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { controller.resetWithError() } }