ui: add QR CodeScanner helpers
This commit is contained in:
@@ -137,6 +137,9 @@
|
||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB82804A2740006080F /* EventView.swift */; };
|
||||
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */; };
|
||||
4C7D09592A05BEAD00943473 /* KeyboardVisible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */; };
|
||||
4C7D096D2A0AEA0400943473 /* CodeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096A2A0AEA0400943473 /* CodeScanner.swift */; };
|
||||
4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096B2A0AEA0400943473 /* ScannerCoordinator.swift */; };
|
||||
4C7D096F2A0AEA0400943473 /* ScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096C2A0AEA0400943473 /* ScannerViewController.swift */; };
|
||||
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
||||
4C8682872814DE470026224F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; };
|
||||
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */; };
|
||||
@@ -545,6 +548,9 @@
|
||||
4C75EFB82804A2740006080F /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = "<group>"; };
|
||||
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = "<group>"; };
|
||||
4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardVisible.swift; sourceTree = "<group>"; };
|
||||
4C7D096A2A0AEA0400943473 /* CodeScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeScanner.swift; sourceTree = "<group>"; };
|
||||
4C7D096B2A0AEA0400943473 /* ScannerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScannerCoordinator.swift; sourceTree = "<group>"; };
|
||||
4C7D096C2A0AEA0400943473 /* ScannerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScannerViewController.swift; sourceTree = "<group>"; };
|
||||
4C7FF7D42823313F009601DB /* Mentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mentions.swift; sourceTree = "<group>"; };
|
||||
4C8682862814DE470026224F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibleAttribute.swift; sourceTree = "<group>"; };
|
||||
@@ -906,6 +912,7 @@
|
||||
4C75EFA227FA576C0006080F /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C7D09692A0AEA0400943473 /* CodeScanner */,
|
||||
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
|
||||
4C1A9A1B29DDCF8B00516EAC /* Settings */,
|
||||
4CFF8F6129CC9A80008DB934 /* Images */,
|
||||
@@ -987,6 +994,16 @@
|
||||
path = Nostr;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C7D09692A0AEA0400943473 /* CodeScanner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C7D096A2A0AEA0400943473 /* CodeScanner.swift */,
|
||||
4C7D096B2A0AEA0400943473 /* ScannerCoordinator.swift */,
|
||||
4C7D096C2A0AEA0400943473 /* ScannerViewController.swift */,
|
||||
);
|
||||
path = CodeScanner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C7FF7D628233637009601DB /* Util */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1574,6 +1591,7 @@
|
||||
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
|
||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
||||
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
||||
4C7D096D2A0AEA0400943473 /* CodeScanner.swift in Sources */,
|
||||
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
|
||||
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */,
|
||||
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */,
|
||||
@@ -1596,6 +1614,7 @@
|
||||
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
||||
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */,
|
||||
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */,
|
||||
4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */,
|
||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
|
||||
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
||||
@@ -1696,6 +1715,7 @@
|
||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
||||
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */,
|
||||
4C7D096F2A0AEA0400943473 /* ScannerViewController.swift in Sources */,
|
||||
4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */,
|
||||
4CC7AAF6297F1A6A00430951 /* EventBody.swift in Sources */,
|
||||
3165648B295B70D500C64604 /* LinkView.swift in Sources */,
|
||||
|
||||
114
damus/Views/CodeScanner/CodeScanner.swift
Normal file
114
damus/Views/CodeScanner/CodeScanner.swift
Normal file
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// CodeScanner.swift
|
||||
// https://github.com/twostraws/CodeScanner
|
||||
//
|
||||
// Created by Paul Hudson on 14/12/2021.
|
||||
// Copyright © 2021 Paul Hudson. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import SwiftUI
|
||||
|
||||
/// An enum describing the ways CodeScannerView can hit scanning problems.
|
||||
public enum ScanError: Error {
|
||||
/// The camera could not be accessed.
|
||||
case badInput
|
||||
|
||||
/// The camera was not capable of scanning the requested codes.
|
||||
case badOutput
|
||||
|
||||
/// Initialization failed.
|
||||
case initError(_ error: Error)
|
||||
}
|
||||
|
||||
/// The result from a successful scan: the string that was scanned, and also the type of data that was found.
|
||||
/// The type is useful for times when you've asked to scan several different code types at the same time, because
|
||||
/// it will report the exact code type that was found.
|
||||
public struct ScanResult {
|
||||
/// The contents of the code.
|
||||
public let string: String
|
||||
|
||||
/// The type of code that was matched.
|
||||
public let type: AVMetadataObject.ObjectType
|
||||
}
|
||||
|
||||
/// The operating mode for CodeScannerView.
|
||||
public enum ScanMode {
|
||||
/// Scan exactly one code, then stop.
|
||||
case once
|
||||
|
||||
/// Scan each code no more than once.
|
||||
case oncePerCode
|
||||
|
||||
/// Keep scanning all codes until dismissed.
|
||||
case continuous
|
||||
}
|
||||
|
||||
/// A SwiftUI view that is able to scan barcodes, QR codes, and more, and send back what was found.
|
||||
/// To use, set `codeTypes` to be an array of things to scan for, e.g. `[.qr]`, and set `completion` to
|
||||
/// a closure that will be called when scanning has finished. This will be sent the string that was detected or a `ScanError`.
|
||||
/// For testing inside the simulator, set the `simulatedData` property to some test data you want to send back.
|
||||
public struct CodeScannerView: UIViewControllerRepresentable {
|
||||
|
||||
public let codeTypes: [AVMetadataObject.ObjectType]
|
||||
public let scanMode: ScanMode
|
||||
public let scanInterval: Double
|
||||
public let showViewfinder: Bool
|
||||
public var simulatedData = ""
|
||||
public var shouldVibrateOnSuccess: Bool
|
||||
public var isTorchOn: Bool
|
||||
public var isGalleryPresented: Binding<Bool>
|
||||
public var videoCaptureDevice: AVCaptureDevice?
|
||||
public var completion: (Result<ScanResult, ScanError>) -> Void
|
||||
|
||||
public init(
|
||||
codeTypes: [AVMetadataObject.ObjectType],
|
||||
scanMode: ScanMode = .once,
|
||||
scanInterval: Double = 2.0,
|
||||
showViewfinder: Bool = false,
|
||||
simulatedData: String = "",
|
||||
shouldVibrateOnSuccess: Bool = true,
|
||||
isTorchOn: Bool = false,
|
||||
isGalleryPresented: Binding<Bool> = .constant(false),
|
||||
videoCaptureDevice: AVCaptureDevice? = AVCaptureDevice.default(for: .video),
|
||||
completion: @escaping (Result<ScanResult, ScanError>) -> Void
|
||||
) {
|
||||
self.codeTypes = codeTypes
|
||||
self.scanMode = scanMode
|
||||
self.showViewfinder = showViewfinder
|
||||
self.scanInterval = scanInterval
|
||||
self.simulatedData = simulatedData
|
||||
self.shouldVibrateOnSuccess = shouldVibrateOnSuccess
|
||||
self.isTorchOn = isTorchOn
|
||||
self.isGalleryPresented = isGalleryPresented
|
||||
self.videoCaptureDevice = videoCaptureDevice
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
public func makeCoordinator() -> ScannerCoordinator {
|
||||
ScannerCoordinator(parent: self)
|
||||
}
|
||||
|
||||
public func makeUIViewController(context: Context) -> ScannerViewController {
|
||||
let viewController = ScannerViewController(showViewfinder: showViewfinder)
|
||||
viewController.delegate = context.coordinator
|
||||
return viewController
|
||||
}
|
||||
|
||||
public func updateUIViewController(_ uiViewController: ScannerViewController, context: Context) {
|
||||
uiViewController.updateViewController(
|
||||
isTorchOn: isTorchOn,
|
||||
isGalleryPresented: isGalleryPresented.wrappedValue
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@available(macCatalyst 14.0, *)
|
||||
struct CodeScannerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CodeScannerView(codeTypes: [.qr]) { result in
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
75
damus/Views/CodeScanner/ScannerCoordinator.swift
Normal file
75
damus/Views/CodeScanner/ScannerCoordinator.swift
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// CodeScanner.swift
|
||||
// https://github.com/twostraws/CodeScanner
|
||||
//
|
||||
// Created by Paul Hudson on 14/12/2021.
|
||||
// Copyright © 2021 Paul Hudson. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import SwiftUI
|
||||
|
||||
extension CodeScannerView {
|
||||
@available(macCatalyst 14.0, *)
|
||||
public class ScannerCoordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
|
||||
var parent: CodeScannerView
|
||||
var codesFound = Set<String>()
|
||||
var didFinishScanning = false
|
||||
var lastTime = Date(timeIntervalSince1970: 0)
|
||||
|
||||
init(parent: CodeScannerView) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
codesFound.removeAll()
|
||||
didFinishScanning = false
|
||||
lastTime = Date(timeIntervalSince1970: 0)
|
||||
}
|
||||
|
||||
public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
||||
if let metadataObject = metadataObjects.first {
|
||||
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
|
||||
guard let stringValue = readableObject.stringValue else { return }
|
||||
guard didFinishScanning == false else { return }
|
||||
let result = ScanResult(string: stringValue, type: readableObject.type)
|
||||
|
||||
switch parent.scanMode {
|
||||
case .once:
|
||||
found(result)
|
||||
// make sure we only trigger scan once per use
|
||||
didFinishScanning = true
|
||||
|
||||
case .oncePerCode:
|
||||
if !codesFound.contains(stringValue) {
|
||||
codesFound.insert(stringValue)
|
||||
found(result)
|
||||
}
|
||||
|
||||
case .continuous:
|
||||
if isPastScanInterval() {
|
||||
found(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isPastScanInterval() -> Bool {
|
||||
Date().timeIntervalSince(lastTime) >= parent.scanInterval
|
||||
}
|
||||
|
||||
func found(_ result: ScanResult) {
|
||||
lastTime = Date()
|
||||
|
||||
if parent.shouldVibrateOnSuccess {
|
||||
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
|
||||
}
|
||||
|
||||
parent.completion(.success(result))
|
||||
}
|
||||
|
||||
func didFail(reason: ScanError) {
|
||||
parent.completion(.failure(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
300
damus/Views/CodeScanner/ScannerViewController.swift
Normal file
300
damus/Views/CodeScanner/ScannerViewController.swift
Normal file
@@ -0,0 +1,300 @@
|
||||
//
|
||||
// CodeScanner.swift
|
||||
// https://github.com/twostraws/CodeScanner
|
||||
//
|
||||
// Created by Paul Hudson on 14/12/2021.
|
||||
// Copyright © 2021 Paul Hudson. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import UIKit
|
||||
|
||||
extension CodeScannerView {
|
||||
|
||||
@available(macCatalyst 14.0, *)
|
||||
public class ScannerViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
|
||||
var delegate: ScannerCoordinator?
|
||||
private let showViewfinder: Bool
|
||||
|
||||
private var isGalleryShowing: Bool = false {
|
||||
didSet {
|
||||
// Update binding
|
||||
if delegate?.parent.isGalleryPresented.wrappedValue != isGalleryShowing {
|
||||
delegate?.parent.isGalleryPresented.wrappedValue = isGalleryShowing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(showViewfinder: Bool = false) {
|
||||
self.showViewfinder = showViewfinder
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
self.showViewfinder = false
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
func openGallery() {
|
||||
isGalleryShowing = true
|
||||
let imagePicker = UIImagePickerController()
|
||||
imagePicker.delegate = self
|
||||
present(imagePicker, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func openGalleryFromButton(_ sender: UIButton) {
|
||||
openGallery()
|
||||
}
|
||||
|
||||
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
|
||||
isGalleryShowing = false
|
||||
|
||||
if let qrcodeImg = info[.originalImage] as? UIImage {
|
||||
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])!
|
||||
let ciImage = CIImage(image:qrcodeImg)!
|
||||
var qrCodeLink = ""
|
||||
|
||||
let features = detector.features(in: ciImage)
|
||||
|
||||
for feature in features as! [CIQRCodeFeature] {
|
||||
qrCodeLink += feature.messageString!
|
||||
}
|
||||
|
||||
if qrCodeLink == "" {
|
||||
delegate?.didFail(reason: .badOutput)
|
||||
} else {
|
||||
let result = ScanResult(string: qrCodeLink, type: .qr)
|
||||
delegate?.found(result)
|
||||
}
|
||||
} else {
|
||||
print("Something went wrong")
|
||||
}
|
||||
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
||||
isGalleryShowing = false
|
||||
}
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
override public func loadView() {
|
||||
view = UIView()
|
||||
view.isUserInteractionEnabled = true
|
||||
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.text = "You're running in the simulator, which means the camera isn't available. Tap anywhere to send back some simulated data."
|
||||
label.textAlignment = .center
|
||||
|
||||
let button = UIButton()
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.setTitle("Select a custom image", for: .normal)
|
||||
button.setTitleColor(UIColor.systemBlue, for: .normal)
|
||||
button.setTitleColor(UIColor.gray, for: .highlighted)
|
||||
button.addTarget(self, action: #selector(openGalleryFromButton), for: .touchUpInside)
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = 50
|
||||
stackView.addArrangedSubview(label)
|
||||
stackView.addArrangedSubview(button)
|
||||
|
||||
view.addSubview(stackView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
button.heightAnchor.constraint(equalToConstant: 50),
|
||||
stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
|
||||
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
guard let simulatedData = delegate?.parent.simulatedData else {
|
||||
print("Simulated Data Not Provided!")
|
||||
return
|
||||
}
|
||||
|
||||
// Send back their simulated data, as if it was one of the types they were scanning for
|
||||
let result = ScanResult(string: simulatedData, type: delegate?.parent.codeTypes.first ?? .qr)
|
||||
delegate?.found(result)
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
var captureSession: AVCaptureSession!
|
||||
var previewLayer: AVCaptureVideoPreviewLayer!
|
||||
let fallbackVideoCaptureDevice = AVCaptureDevice.default(for: .video)
|
||||
|
||||
private lazy var viewFinder: UIImageView? = {
|
||||
guard let image = UIImage(named: "viewfinder", in: .main, with: nil) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let imageView = UIImageView(image: image)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(updateOrientation),
|
||||
name: Notification.Name("UIDeviceOrientationDidChangeNotification"),
|
||||
object: nil)
|
||||
|
||||
view.backgroundColor = UIColor.black
|
||||
captureSession = AVCaptureSession()
|
||||
|
||||
guard let videoCaptureDevice = delegate?.parent.videoCaptureDevice ?? fallbackVideoCaptureDevice else {
|
||||
return
|
||||
}
|
||||
|
||||
let videoInput: AVCaptureDeviceInput
|
||||
|
||||
do {
|
||||
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
|
||||
} catch {
|
||||
delegate?.didFail(reason: .initError(error))
|
||||
return
|
||||
}
|
||||
|
||||
if (captureSession.canAddInput(videoInput)) {
|
||||
captureSession.addInput(videoInput)
|
||||
} else {
|
||||
delegate?.didFail(reason: .badInput)
|
||||
return
|
||||
}
|
||||
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
|
||||
if (captureSession.canAddOutput(metadataOutput)) {
|
||||
captureSession.addOutput(metadataOutput)
|
||||
|
||||
metadataOutput.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main)
|
||||
metadataOutput.metadataObjectTypes = delegate?.parent.codeTypes
|
||||
} else {
|
||||
delegate?.didFail(reason: .badOutput)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override public func viewWillLayoutSubviews() {
|
||||
previewLayer?.frame = view.layer.bounds
|
||||
}
|
||||
|
||||
@objc func updateOrientation() {
|
||||
guard let orientation = view.window?.windowScene?.interfaceOrientation else { return }
|
||||
guard let connection = captureSession.connections.last, connection.isVideoOrientationSupported else { return }
|
||||
connection.videoOrientation = AVCaptureVideoOrientation(rawValue: orientation.rawValue) ?? .portrait
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
updateOrientation()
|
||||
}
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if previewLayer == nil {
|
||||
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
||||
}
|
||||
|
||||
previewLayer.frame = view.layer.bounds
|
||||
previewLayer.videoGravity = .resizeAspectFill
|
||||
view.layer.addSublayer(previewLayer)
|
||||
addviewfinder()
|
||||
|
||||
delegate?.reset()
|
||||
|
||||
if (captureSession?.isRunning == false) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.captureSession.startRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addviewfinder() {
|
||||
guard showViewfinder, let imageView = viewFinder else { return }
|
||||
|
||||
view.addSubview(imageView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
imageView.widthAnchor.constraint(equalToConstant: 200),
|
||||
imageView.heightAnchor.constraint(equalToConstant: 200),
|
||||
])
|
||||
}
|
||||
|
||||
override public func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
if (captureSession?.isRunning == true) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.captureSession.stopRunning()
|
||||
}
|
||||
}
|
||||
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
override public var prefersStatusBarHidden: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
.all
|
||||
}
|
||||
|
||||
/** Touch the screen for autofocus */
|
||||
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
guard touches.first?.view == view,
|
||||
let touchPoint = touches.first,
|
||||
let device = delegate?.parent.videoCaptureDevice ?? fallbackVideoCaptureDevice
|
||||
else { return }
|
||||
|
||||
let videoView = view
|
||||
let screenSize = videoView!.bounds.size
|
||||
let xPoint = touchPoint.location(in: videoView).y / screenSize.height
|
||||
let yPoint = 1.0 - touchPoint.location(in: videoView).x / screenSize.width
|
||||
let focusPoint = CGPoint(x: xPoint, y: yPoint)
|
||||
|
||||
do {
|
||||
try device.lockForConfiguration()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
// Focus to the correct point, make continiuous focus and exposure so the point stays sharp when moving the device closer
|
||||
device.focusPointOfInterest = focusPoint
|
||||
device.focusMode = .continuousAutoFocus
|
||||
device.exposurePointOfInterest = focusPoint
|
||||
device.exposureMode = AVCaptureDevice.ExposureMode.continuousAutoExposure
|
||||
device.unlockForConfiguration()
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
func updateViewController(isTorchOn: Bool, isGalleryPresented: Bool) {
|
||||
if let backCamera = AVCaptureDevice.default(for: AVMediaType.video),
|
||||
backCamera.hasTorch
|
||||
{
|
||||
try? backCamera.lockForConfiguration()
|
||||
backCamera.torchMode = isTorchOn ? .on : .off
|
||||
backCamera.unlockForConfiguration()
|
||||
}
|
||||
|
||||
if isGalleryPresented && !isGalleryShowing {
|
||||
openGallery()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user