mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			151 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			151 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import UIKit
 | |
| import AVFoundation
 | |
| import ZXingObjC
 | |
| import SessionUIKit
 | |
| 
 | |
| protocol QRScannerDelegate: AnyObject {
 | |
|     func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String)
 | |
| }
 | |
| 
 | |
| class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ZXCaptureDelegate {
 | |
|     public weak var scanDelegate: QRScannerDelegate?
 | |
|     
 | |
|     private let captureQueue: DispatchQueue = DispatchQueue.global(qos: .default)
 | |
|     private var capture: ZXCapture?
 | |
|     private var captureEnabled: Bool = false
 | |
|     
 | |
|     // MARK: - Initialization
 | |
|     
 | |
|     deinit {
 | |
|         self.capture?.layer.removeFromSuperlayer()
 | |
|     }
 | |
|     
 | |
|     // MARK: - Components
 | |
|     
 | |
|     private let maskingView: UIView = {
 | |
|         let result: OWSBezierPathView = OWSBezierPathView()
 | |
|         result.configureShapeLayerBlock = { layer, bounds in
 | |
|             // Add a circular mask
 | |
|             let path: UIBezierPath = UIBezierPath(rect: bounds)
 | |
|             let margin: CGFloat = ScaleFromIPhone5To7Plus(24, 48)
 | |
|             let radius: CGFloat = ((min(bounds.size.width, bounds.size.height) * 0.5) - margin)
 | |
| 
 | |
|             // Center the circle's bounding rectangle
 | |
|             let circleRect: CGRect = CGRect(
 | |
|                 x: ((bounds.size.width * 0.5) - radius),
 | |
|                 y: ((bounds.size.height * 0.5) - radius),
 | |
|                 width: (radius * 2),
 | |
|                 height: (radius * 2)
 | |
|             )
 | |
|             let circlePath: UIBezierPath = UIBezierPath.init(
 | |
|                 roundedRect: circleRect,
 | |
|                 cornerRadius: 16
 | |
|             )
 | |
|             path.append(circlePath)
 | |
|             path.usesEvenOddFillRule = true
 | |
| 
 | |
|             layer.path = path.cgPath
 | |
|             layer.fillRule = .evenOdd
 | |
|             layer.themeFillColor = .black
 | |
|             layer.opacity = 0.32
 | |
|         }
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     // MARK: - Lifecycle
 | |
|     
 | |
|     override func loadView() {
 | |
|         super.loadView()
 | |
|         
 | |
|         self.view.addSubview(maskingView)
 | |
|         maskingView.pin(to: self.view)
 | |
|     }
 | |
|     
 | |
|     override func viewWillAppear(_ animated: Bool) {
 | |
|         super.viewWillAppear(animated)
 | |
|         
 | |
|         if captureEnabled {
 | |
|             self.startCapture()
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     override func viewWillDisappear(_ animated: Bool) {
 | |
|         super.viewWillDisappear(animated)
 | |
|         
 | |
|         self.stopCapture()
 | |
|     }
 | |
|     
 | |
|     override func viewWillLayoutSubviews() {
 | |
|         super.viewWillLayoutSubviews()
 | |
|         
 | |
|         // Note: When accessing 'capture.layer' if the setup hasn't been completed it
 | |
|         // will result in a layout being triggered which creates an infinite loop, this
 | |
|         // check prevents that case
 | |
|         if let capture: ZXCapture = self.capture {
 | |
|             capture.layer.frame = self.view.bounds
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // MARK: - Functions
 | |
|     
 | |
|     public func startCapture() {
 | |
|         self.captureEnabled = true
 | |
|         
 | |
|         // Note: The simulator doesn't support video but if we do try to start an
 | |
|         // AVCaptureSession it seems to hang on that particular thread indefinitely
 | |
|         // this will prevent us from trying to start a session on the simulator
 | |
|         #if targetEnvironment(simulator)
 | |
|         #else
 | |
|             if self.capture == nil {
 | |
|                 self.captureQueue.async { [weak self] in
 | |
|                     let capture: ZXCapture = ZXCapture()
 | |
|                     capture.camera = capture.back()
 | |
|                     capture.focusMode = .autoFocus
 | |
|                     capture.delegate = self
 | |
|                     capture.start()
 | |
|                     
 | |
|                     // Note: When accessing the 'layer' for the first time it will create
 | |
|                     // an instance of 'AVCaptureVideoPreviewLayer', this can hang a little
 | |
|                     // so we do this on the background thread first
 | |
|                     if capture.layer != nil {}
 | |
| 
 | |
|                     DispatchQueue.main.async {
 | |
|                         capture.layer.frame = (self?.view.bounds ?? .zero)
 | |
|                         self?.view.layer.addSublayer(capture.layer)
 | |
|                         
 | |
|                         if let maskingView: UIView = self?.maskingView {
 | |
|                             self?.view.bringSubviewToFront(maskingView)
 | |
|                         }
 | |
|                     
 | |
|                         self?.capture = capture
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 self.capture?.start()
 | |
|             }
 | |
|         #endif
 | |
|     }
 | |
| 
 | |
|     private func stopCapture() {
 | |
|         self.captureEnabled = false
 | |
|         self.captureQueue.async { [weak self] in
 | |
|             self?.capture?.stop()
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     internal func captureResult(_ capture: ZXCapture, result: ZXResult) {
 | |
|         guard self.captureEnabled else { return }
 | |
|         
 | |
|         self.stopCapture()
 | |
|         
 | |
|         // Vibrate
 | |
|         AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
 | |
|         
 | |
|         self.scanDelegate?.controller(self, didDetectQRCodeWith: result.text)
 | |
|     }
 | |
| }
 |