Skip to content

Instantly share code, notes, and snippets.

@no13bus
Last active October 23, 2023 12:57
Show Gist options
  • Save no13bus/ab7954e1925b8edb2674ac016bbfd8d8 to your computer and use it in GitHub Desktop.
Save no13bus/ab7954e1925b8edb2674ac016bbfd8d8 to your computer and use it in GitHub Desktop.
ContentView.swift
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) {
}
}
//
// 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()
}
}
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