Last active
October 23, 2023 12:57
-
-
Save no13bus/ab7954e1925b8edb2674ac016bbfd8d8 to your computer and use it in GitHub Desktop.
ContentView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import AVKit | |
struct CameraView: UIViewRepresentable { | |
var frameSize: CGSize | |
@Binding var session: AVCaptureSession | |
func makeUIView(context: Context) -> UIView { | |
let view = UIViewType(frame: CGRect(origin: .zero, size: frameSize)) | |
view.backgroundColor = .clear | |
let cameraLayer = AVCaptureVideoPreviewLayer(session: session) | |
cameraLayer.frame = .init(origin: .zero, size: frameSize) | |
cameraLayer.videoGravity = .resizeAspectFill | |
cameraLayer.masksToBounds = true | |
view.layer.addSublayer(cameraLayer) | |
return view | |
} | |
func updateUIView(_ uiView: UIViewType, context: Context) { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// QRCodeScanner | |
// | |
// Created by Qihui Jia on 2023-10-20. | |
// | |
import AVKit | |
import SwiftUI | |
import AVFoundation | |
import SwiftUI | |
enum Permission: String{ | |
case idle = "Not Determinded" | |
case approved = "Access Granted" | |
case denied = "Access Denied" | |
} | |
struct ContentView: View { | |
@State private var isScanning: Bool = false | |
@State private var session: AVCaptureSession = .init() | |
@State private var qrOutput: AVCaptureMetadataOutput = .init() | |
@State private var errorMessage: String = "" | |
@State private var showError: Bool = false | |
@State private var cameraPermission: Permission = .idle | |
@Environment(\.openURL) private var openURL | |
@StateObject private var qrDelegate = QRScannerDelegate() | |
@State private var inputText: String = "" | |
var body: some View { | |
VStack { | |
Text("Place the QR code inside the area") | |
.font(.title3) | |
.foregroundColor(.black.opacity(0.8)) | |
.padding(.top, 20) | |
GeometryReader { | |
let size = $0.size | |
ZStack { | |
CameraView(frameSize: CGSize(width: size.width, height: size.width), session: $session) | |
.scaleEffect(0.97) | |
ForEach(0 ... 4, id: \.self) { index in | |
let rotation = Double(index) * 90 | |
RoundedRectangle(cornerRadius: 2, style: .circular) | |
.trim(from: 0.61, to: 0.64) | |
.stroke(Color.blue, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round)) | |
.rotationEffect(.init(degrees: rotation)) | |
} | |
} | |
.frame(width: size.width, height: size.width) | |
.overlay(alignment: .top, content: { | |
Rectangle() | |
.fill(Color.blue) | |
.frame(height: 2.5) | |
.shadow(color: .black.opacity(0.8), radius: 8, x: 0, y: isScanning ? 15 : -15) | |
.offset(y: isScanning ? size.width : 0) | |
}) | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
} | |
.padding(.horizontal, 45) | |
Spacer(minLength: 0) | |
TextField("input article name", text: $inputText) | |
.textFieldStyle(RoundedBorderTextFieldStyle()) | |
.padding() | |
.onSubmit { | |
print("aaaa:\(inputText)") | |
} | |
Spacer(minLength: 150) | |
} | |
.padding(15) | |
.onAppear(perform: checkCameraPermission) | |
.alert(errorMessage, isPresented: $showError) { | |
if cameraPermission == .denied { | |
Button("Settings") { | |
let settingsString = UIApplication.openSettingsURLString | |
if let settingsURL = URL(string: settingsString) { | |
openURL(settingsURL) | |
} | |
} | |
Button("Cancel", role: .cancel) {} | |
} | |
} | |
.onChange(of: qrDelegate.scannedCode) { newValue in | |
if let code = newValue { | |
inputText = code | |
session.stopRunning() | |
deActivateScannerAnimation() | |
qrDelegate.scannedCode = nil | |
} | |
} | |
} | |
func checkCameraPermission() { | |
Task { | |
switch AVCaptureDevice.authorizationStatus(for: .video) { | |
case .authorized: | |
cameraPermission = .approved | |
if session.inputs.isEmpty { | |
setupCamera() | |
} else { | |
session.startRunning() | |
} | |
case .notDetermined: | |
if await AVCaptureDevice.requestAccess(for: .video) { | |
cameraPermission = .approved | |
setupCamera() | |
} else { | |
cameraPermission = .denied | |
presentError("Please provide access to camera for scan the code") | |
} | |
case .denied, .restricted: | |
cameraPermission = .denied | |
presentError("Please provide access to camera for scan the code") | |
default: break | |
} | |
} | |
} | |
func reactivateCamera() { | |
DispatchQueue.global(qos: .background).async { | |
session.startRunning() | |
} | |
} | |
func activateScannerAnimation() { | |
withAnimation(.easeInOut(duration: 0.85).delay(0.1).repeatForever(autoreverses: true)) { | |
isScanning = true | |
} | |
} | |
func deActivateScannerAnimation() { | |
withAnimation(.easeInOut(duration: 0.85)) { | |
isScanning = false | |
} | |
} | |
func playScanSound() { | |
if let BDURL = Bundle.main.url(forResource: "beep", withExtension: "mp3") { | |
do { | |
let BDPlayer = try AVAudioPlayer(contentsOf: BDURL) /// make the audio player | |
BDPlayer.volume = 5 | |
BDPlayer.play() | |
} catch { | |
print("Couldn't play audio. Error: \(error)") | |
} | |
} | |
} | |
func setupCamera() { | |
do { | |
guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices.first else { | |
presentError("Unknown Error") | |
return | |
} | |
let input = try AVCaptureDeviceInput(device: device) | |
guard session.canAddInput(input), session.canAddOutput(qrOutput) else { | |
presentError("Unknown Error") | |
return | |
} | |
session.beginConfiguration() | |
session.addInput(input) | |
session.addOutput(qrOutput) | |
qrOutput.metadataObjectTypes = [.qr] | |
qrOutput.setMetadataObjectsDelegate(qrDelegate, queue: .main) | |
session.commitConfiguration() | |
DispatchQueue.global(qos: .background).async { | |
session.startRunning() | |
} | |
activateScannerAnimation() | |
} catch { | |
presentError(error.localizedDescription) | |
} | |
} | |
func presentError(_ message: String) { | |
errorMessage = message | |
showError.toggle() | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import AVKit | |
class QRScannerDelegate: NSObject, ObservableObject, AVCaptureMetadataOutputObjectsDelegate { | |
@Published var scannedCode: String? | |
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { | |
if let metaObject = metadataObjects.first{ | |
guard let readableObject = metaObject as? AVMetadataMachineReadableCodeObject else { return } | |
guard let Code = readableObject.stringValue else { return } | |
self.scannedCode = Code | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment