mobile_local_restore/ios/CameraView.swift

781 lines
28 KiB
Swift

import UIKit
import AVFoundation
import React
@objc(CameraView)
class CameraView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate {
var captureSession: AVCaptureSession!
var videoOutput: AVCaptureVideoDataOutput?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var overlayImageView: UIImageView!
var titleLabel: UILabel!
var bottomLeftLabel: UILabel!
var cardData:NSArray!
var CARD_SCANNING_TYPE:Int!
var classData:NSDictionary?
var optionData:NSDictionary!
//var cardResult:NSDictionary!
var cardResult: [[String: Any]] = []
var correctAnswer:NSString! = ""
var scanCardButton1: UIButton!
var saveCardButton:UIButton!
var startScan:Bool = false
var attendanceTotalLabel: UILabel!
var attendanceTotalCount: UILabel!
var standardLabel: UILabel!
var standardName: UILabel!
private var timer: Timer?
private let timeLabel: UILabel = {
let label = UILabel()
//label.backgroundColor = UIColor.white.withAlphaComponent(0.7)
label.textColor = .white
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
return label
}()
var answers = [
["is_correct": false, "option": "A"],
["is_correct": false, "option": "B"],
["is_correct": false, "option": "C"],
["is_correct": false, "option": "D"]
]
@objc var title: String? {
didSet {
titleLabel.text = title
}
}
@objc var type: NSNumber? {
didSet {
if let type = type {
CARD_SCANNING_TYPE = type.intValue
if (CARD_SCANNING_TYPE == 1){
createCorrectAnsLayout(context: self, scanCardButton:scanCardButton1)
}
}
}
}
@objc var cards: NSArray? {
didSet {
if let cards = cards {
cardData = cards
addCardsToTopRight(cards)
let cardResultCount = cardResult.count
let cardsCount = cards.count
attendanceTotalCount.text = "\(cardResultCount) / \(cardsCount)"
}
}
}
@objc var classes: NSDictionary? {
didSet {
if let classes = classes {
classData = classes
if let className = classes["class_name"] as? String {
standardName.text = className
}
}
}
}
@objc var options: NSDictionary? {
didSet {
if let options = options {
optionData = options
if let standard_Label = options["standardLabel"] as? String {
standardLabel.text = standard_Label
}
if let attendance_label = options["attendanceLabel"] as? String {
attendanceTotalLabel.text = attendance_label
}
}
}
}
@objc func setIntValue(_ val: NSNumber) {
}
@objc var onChange: RCTBubblingEventBlock?
override init(frame: CGRect) {
super.init(frame: frame)
setupCaptureSession()
setupOverlayImageView()
setupTitleLabel()
setupTimeLabel()
setupOrientationChangeObserver()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupCaptureSession()
setupOverlayImageView()
setupTitleLabel()
setupTimeLabel()
setupOrientationChangeObserver()
}
func setupOrientationChangeObserver() {
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
// NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil)
}
@objc func orientationChanged() {
let orientation = UIDevice.current.orientation
switch orientation {
case .landscapeLeft, .landscapeRight:
adjustImageViewForLandscape()
default:
break
}
}
func adjustImageViewForLandscape() {
overlayImageView.frame = self.bounds
overlayImageView.setNeedsLayout()
}
var isAuthorized: Bool {
get async {
let status = AVCaptureDevice.authorizationStatus(for: .video)
// Determine if the user previously authorized camera access.
var isAuthorized = status == .authorized
// If the system hasn't determined the user's authorization status,
// explicitly prompt them for approval.
if status == .notDetermined {
isAuthorized = await AVCaptureDevice.requestAccess(for: .video)
}
return isAuthorized
}
}
private func setupCaptureSession() {
captureSession = AVCaptureSession()
guard let captureSession = captureSession else { return }
guard let videoCaptureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { return }
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
print("Error creating video input: \(error)")
return
}
if captureSession.canAddInput(videoInput) {
captureSession.addInput(videoInput)
} else {
print("Could not add video input to capture session")
return
}
videoOutput = AVCaptureVideoDataOutput()
guard let videoOutput = videoOutput else { return }
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
videoOutput.alwaysDiscardsLateVideoFrames = true
videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
if captureSession.canAddOutput(videoOutput) {
captureSession.addOutput(videoOutput)
} else {
print("Could not add video output to capture session")
return
}
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
guard let videoPreviewLayer = videoPreviewLayer else { return }
videoPreviewLayer.videoGravity = .resizeAspectFill
videoPreviewLayer.frame = self.layer.bounds
self.layer.addSublayer(videoPreviewLayer)
captureSession.startRunning()
}
override func layoutSubviews() {
super.layoutSubviews()
videoPreviewLayer?.frame = self.bounds
}
private func setupTimeLabel() {
timeLabel.frame = CGRect(x: 10, y: 10, width: 100, height: 30)
self.addSubview(timeLabel)
bringSubviewToFront(timeLabel)
timeLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
timeLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 20),
timeLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20)
])
// Update the time every second
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.updateTimeLabel()
}
}
private func updateTimeLabel() {
let formatter = DateFormatter()
formatter.dateFormat = "h:mm:ss a" // 12:11:11 PM format
let currentTime = formatter.string(from: Date())
timeLabel.text = currentTime
}
deinit {
captureSession.stopRunning()
timer?.invalidate()
}
private func setupTitleLabel() {
let attendanceLayout = createAttendanceLayout()
self.addSubview(attendanceLayout)
bringSubviewToFront(attendanceLayout)
let standardLayout = createStandardLayout(relativeTo: attendanceLayout)
self.addSubview(standardLayout)
bringSubviewToFront(standardLayout)
scanCardButton1 = UIButton(type: .system)
scanCardButton1.setTitleColor(.gray, for: .normal)
scanCardButton1.translatesAutoresizingMaskIntoConstraints = false
scanCardButton1.addTarget(self, action: #selector(scanCard), for: .touchUpInside)
self.addSubview(scanCardButton1)
bringSubviewToFront(scanCardButton1)
let buttonSize: CGFloat = 60
NSLayoutConstraint.activate([
scanCardButton1.widthAnchor.constraint(equalToConstant: buttonSize),
scanCardButton1.heightAnchor.constraint(equalToConstant: buttonSize),
scanCardButton1.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10),
scanCardButton1.centerXAnchor.constraint(equalTo: self.centerXAnchor)
])
scanCardButton1.backgroundColor = .white
scanCardButton1.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
let image = UIImage(systemName: "camera")?.withRenderingMode(.alwaysTemplate)
scanCardButton1.setImage(image, for: .normal)
scanCardButton1.tintColor = .gray
// Making the button round and setting border color to red
scanCardButton1.layer.cornerRadius = buttonSize / 2
scanCardButton1.layer.borderWidth = 3
scanCardButton1.layer.borderColor = UIColor.red.cgColor
scanCardButton1.imageEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
saveCardButton = UIButton(type: .system)
//saveCardButton.setTitle("Save", for: .normal)
saveCardButton.setTitleColor(.blue, for: .normal)
// Set the layout constraints
saveCardButton.translatesAutoresizingMaskIntoConstraints = false
saveCardButton.addTarget(self, action: #selector(saveCard), for: .touchUpInside)
self.addSubview(saveCardButton)
bringSubviewToFront(saveCardButton)
saveCardButton.frame = CGRect(x: 100, y: 100, width: 200, height: 50)
//saveCardButton.contentEdgeInsets = UIEdgeInsets(top: 40, left: 40, bottom: 40, right: 40)
let saveimage = UIImage(systemName: "doc")?.withRenderingMode(.alwaysTemplate)
saveCardButton.setImage(saveimage, for: .normal)
saveCardButton.tintColor = .gray
let savebuttonSize: CGFloat = 50
NSLayoutConstraint.activate([
saveCardButton.widthAnchor.constraint(equalToConstant: savebuttonSize),
saveCardButton.heightAnchor.constraint(equalToConstant: savebuttonSize),
saveCardButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10),
saveCardButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20)
])
//
saveCardButton.backgroundColor = .white
saveCardButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
saveCardButton.layer.cornerRadius = savebuttonSize / 2
saveCardButton.layer.borderWidth = 2
saveCardButton.layer.borderColor = UIColor.red.cgColor
attendanceTotalCount = createLabel(withText: "0/6")
attendanceTotalLabel = createLabel(withText: "Instant Feedback")
standardName = createLabel(withText: "XII A")
standardLabel = createLabel(withText: "Standard")
// Create horizontal stack views for each row
let row1StackView = UIStackView(arrangedSubviews: [attendanceTotalCount, standardName])
row1StackView.axis = .horizontal
row1StackView.distribution = .fillEqually
row1StackView.spacing = 1
let row2StackView = UIStackView(arrangedSubviews: [attendanceTotalLabel, standardLabel])
row2StackView.axis = .horizontal
row2StackView.distribution = .fillEqually
row2StackView.spacing = 1
// Create a vertical stack view to hold the two rows
let mainStackView = UIStackView(arrangedSubviews: [row1StackView, row2StackView])
mainStackView.axis = .vertical
mainStackView.distribution = .fillEqually
mainStackView.spacing = 1
// Add the main stack view to the view
self.addSubview(mainStackView)
bringSubviewToFront(mainStackView)
mainStackView.translatesAutoresizingMaskIntoConstraints = false
// Set constraints for the main stack view
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
mainStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20),
mainStackView.widthAnchor.constraint(equalToConstant: 200), // Adjust as needed
mainStackView.heightAnchor.constraint(equalToConstant: 50) // Adjust as needed
])
}
@objc func scanCard() {
if (startScan) {
startScan = false;
scanCardButton1.setImage(UIImage(systemName: "camera"), for: .normal)
}else{
startScan = true;
scanCardButton1.setImage(UIImage(systemName: "pause.fill"), for: .normal)
}
}
@objc func saveCard() {
if(startScan){
if let onChange = onChange {
var dictionary: [String: Any] = [:]
if let classData = classData as? [String: Any],
let academicYear = classData["academic_year"] as? Int {
dictionary["academic_year"] = academicYear
} else {
print("Failed to extract academic year")
}
if let academic_year = classData?["academic_year"] as? String {
dictionary["academic_year"] = academic_year
}
if let class_id = classData?["class_id"] as? String {
dictionary["class_id"] = class_id
}
if let subject_id = classData?["subject_id"] as? String {
dictionary["subject_id"] = subject_id
}
dictionary["status"] = true
if (CARD_SCANNING_TYPE == 0) {
dictionary["attendance_details"] = cardResult
}
else if (CARD_SCANNING_TYPE == 1) {
if let name = classData?["name"] as? String {
dictionary["name"] = name
}
if let topics = classData?["topics"] as? String {
dictionary["topics"] = topics
}
if let subject_id = classData?["subject_id"] as? String {
dictionary["subject_id"] = subject_id
}
dictionary["correct_answer"] = correctAnswer
dictionary["answers"] = answers
dictionary["feedback_result"] = cardResult
}
else if (CARD_SCANNING_TYPE == 2) {
dictionary["quiz_result"] = cardResult
}
// onChange(["attendance_details": data, "academic_year": academicYear, "class_id": "35", "status": true, "subject_id": "5"])
if CARD_SCANNING_TYPE == 1 && (correctAnswer == nil || correctAnswer.length == 0){
showToast(message: "Please choose the correct answer.", font: .systemFont(ofSize: 12.0))
}else{
onChange(dictionary)
startScan = false;
scanCardButton1.setImage(UIImage(systemName: "camera"), for: .normal)
captureSession.stopRunning()
}
}
}
}
func showToast(message : String, font: UIFont) {
let toastLabel = UILabel(frame: CGRect(x: self.frame.size.width/2 - 75, y: self.frame.size.height-100, width: 200, height: 35))
toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
toastLabel.textColor = UIColor.white
toastLabel.font = font
toastLabel.textAlignment = .center;
toastLabel.text = message
toastLabel.alpha = 1.0
toastLabel.layer.cornerRadius = 10;
toastLabel.clipsToBounds = true
self.addSubview(toastLabel)
bringSubviewToFront(toastLabel)
UIView.animate(withDuration: 4.0, delay: 0.1, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: {(isCompleted) in
toastLabel.removeFromSuperview()
})
}
func createCorrectAnsLayout(context: UIView, scanCardButton: UIButton) -> UIView {
let correctAnsLayout = UIView()
correctAnsLayout.translatesAutoresizingMaskIntoConstraints = false
context.addSubview(correctAnsLayout)
NSLayoutConstraint.activate([
correctAnsLayout.bottomAnchor.constraint(equalTo: context.bottomAnchor, constant: -10),
correctAnsLayout.leadingAnchor.constraint(equalTo: scanCardButton.trailingAnchor, constant: 20),
correctAnsLayout.trailingAnchor.constraint(equalTo: saveCardButton.leadingAnchor, constant: -10)
])
let buttonTitles = ["A", "B", "C", "D"]
var previousButton: UIButton?
for (index, title) in buttonTitles.enumerated() {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.backgroundColor = .gray
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20) // Increase font size
button.layer.cornerRadius = 20 // Make the button rounded
button.layer.masksToBounds = true
button.translatesAutoresizingMaskIntoConstraints = false
button.tag = 1000 + index
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
correctAnsLayout.addSubview(button)
NSLayoutConstraint.activate([
button.topAnchor.constraint(equalTo: correctAnsLayout.topAnchor),
button.bottomAnchor.constraint(equalTo: correctAnsLayout.bottomAnchor),
button.heightAnchor.constraint(equalToConstant: 40),
button.widthAnchor.constraint(equalToConstant: 40)
])
if let previousButton = previousButton {
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: previousButton.trailingAnchor, constant: 10),
button.widthAnchor.constraint(equalTo: previousButton.widthAnchor)
])
} else {
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: correctAnsLayout.leadingAnchor)
])
}
previousButton = button
}
return correctAnsLayout
}
@objc func buttonTapped(_ sender: UIButton) {
// Update the answers array
let tag = sender.tag - 1000
for index in 0..<answers.count {
answers[index]["is_correct"] = (index == tag)
}
let selectedOption = answers[tag]["option"] as! String
correctAnswer = selectedOption as NSString
// Update the button colors
for button in sender.superview!.subviews where button is UIButton {
(button as! UIButton).backgroundColor = .gray
}
sender.backgroundColor = .green
}
func addCardsToTopRight(_ CARD_LISTS: NSArray) {
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(containerView)
bringSubviewToFront(containerView)
let tableLayout = createCardsDesignTableLayout(context: containerView)
containerView.addSubview(tableLayout)
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: self.topAnchor, constant: 20),
containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),
tableLayout.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
tableLayout.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
tableLayout.topAnchor.constraint(equalTo: containerView.topAnchor),
tableLayout.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
var tableRow = UIStackView()
tableRow.axis = .horizontal
tableRow.spacing = 5
//tableRow.alignment = .leading
tableRow.alignment = .center // Centers the buttons vertically
tableRow.distribution = .equalSpacing // Ensures equal spacing between buttons
for (index, card) in CARD_LISTS.enumerated() {
guard let cardDict = card as? [String: Any] else { continue }
if index % 5 == 0 {
tableRow = UIStackView()
tableRow.axis = .horizontal
tableRow.spacing = 5
tableRow.alignment = .center // Centers the buttons vertically
tableRow.distribution = .equalSpacing // Ensures equal spacing between buttons
tableRow.translatesAutoresizingMaskIntoConstraints = false
tableLayout.addArrangedSubview(tableRow)
}
let cardButton = UIButton(type: .system)
if let cardId = cardDict["card_id"] as? Int {
cardButton.setTitle("\(cardId)", for: .normal)
cardButton.tag = cardId
} else if let cardId = cardDict["card_id"] as? String, let cardIdInt = Int(cardId) {
cardButton.setTitle(cardId, for: .normal)
cardButton.tag = cardIdInt
} else {
print("Could not cast card_id to Int for card: \(cardDict)")
}
cardButton.setTitleColor(.white, for: .normal)
cardButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
cardButton.backgroundColor = UIColor.darkGray
cardButton.layer.cornerRadius = 15
tableRow.addArrangedSubview(cardButton)
NSLayoutConstraint.activate([
cardButton.heightAnchor.constraint(equalToConstant: 30),
cardButton.widthAnchor.constraint(equalToConstant: 30)
])
}
}
func createCardLabel(withText text: String) -> UILabel {
let label = UILabel()
label.text = text
label.textColor = .white
label.textAlignment = .center
label.font = UIFont(name: "OpenSans-Bold", size: 17)
label.minimumScaleFactor = 0.5
label.adjustsFontSizeToFitWidth = true
label.backgroundColor = .systemBlue
label.layer.cornerRadius = 10
label.layer.masksToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
label.heightAnchor.constraint(equalToConstant: 55).isActive = true
label.widthAnchor.constraint(equalToConstant: 55).isActive = true
return label
}
func createCardsDesignTableLayout(context: UIView) -> UIStackView {
let cardDesign = UIStackView()
cardDesign.axis = .vertical
cardDesign.spacing = 10
cardDesign.alignment = .leading
cardDesign.translatesAutoresizingMaskIntoConstraints = false
context.addSubview(cardDesign)
bringSubviewToFront(cardDesign)
return cardDesign
}
func createLabel(withText text: String) -> UILabel {
let label = UILabel()
label.text = text
label.textColor = .white
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 11)
return label
}
func createAttendanceLayout() -> UIView {
let attendanceLayout = UIView()
attendanceLayout.translatesAutoresizingMaskIntoConstraints = false
attendanceLayout.backgroundColor = .red // Just for visibility, remove or set your color
attendanceLayout.accessibilityIdentifier = "attendance_layout_id"
return attendanceLayout
}
func createStandardLayout(relativeTo attendanceLayout: UIView) -> UIView {
let standardLayout = UIView()
standardLayout.translatesAutoresizingMaskIntoConstraints = false
standardLayout.backgroundColor = .blue // Just for visibility, remove or set your color
standardLayout.accessibilityIdentifier = "standard_layout_id"
return standardLayout
}
@objc var stringValue: String = "" {
didSet {
// Update the view with the new string value
}
}
func hasValue(cardlists: [Any], key: String, value: Int) -> Bool {
for cardD in cardData {
guard let cardDict = cardD as? [String: Any] else { continue }
if let cardIdString = cardDict[key] as? String, let cardIdInt = Int(cardIdString) {
if cardIdInt == value {
return true
}
}
}
return false
}
private func storeCardResult(_ resultsArray : [[String: Any]]){
for marker in resultsArray {
let id = marker["card_id"] as! Int
let answer = marker["answer"] as! NSString
let cardExists = hasValue(cardlists: cardData as! [Any], key: "card_id", value: id)
if (cardExists) {
if (CARD_SCANNING_TYPE == 0) {
if let index = self.cardResult.firstIndex(where: { $0["card_id"] as! Int == id }) {
self.cardResult[index]["answer"] = answer
} else {
self.cardResult.append(["answer": answer, "attendance": 1, "card_id": id])
}
}else if(CARD_SCANNING_TYPE == 1){
if let index = self.cardResult.firstIndex(where: { $0["card_id"] as! Int == id }) {
self.cardResult[index]["answer"] = answer
} else {
self.cardResult.append(["answer": answer, "attendance": 1, "card_id": id])
}
}else if(CARD_SCANNING_TYPE == 2){
var serial = 0
if let serial_no = classData?["serial_no"] as? Int {
serial = serial_no
}
if let index = self.cardResult.firstIndex(where: { $0["card_id"] as! Int == id }) {
self.cardResult[index]["answer"] = answer
} else {
self.cardResult.append(["answer": answer, "serial_no": serial, "card_id": id])
}
}
}
}
}
private func setupOverlayImageView() {
overlayImageView = UIImageView()
overlayImageView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(overlayImageView)
bringSubviewToFront(overlayImageView)
NSLayoutConstraint.activate([
overlayImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
overlayImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
overlayImageView.topAnchor.constraint(equalTo: self.topAnchor),
overlayImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
func updateOverlayImage(image: UIImage) {
overlayImageView.image = image
bringSubviewToFront(overlayImageView)
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let ciImage = CIImage(cvPixelBuffer: imageBuffer)
let context = CIContext()
guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return }
let uiImage = UIImage(cgImage: cgImage)
DispatchQueue.global(qos: .userInitiated).async {
let result = Wrapper.drawDetectedMarkers(on: uiImage, startScan: self.startScan, card_SCANNING_TYPE:Int32(self.CARD_SCANNING_TYPE)) as NSDictionary
let processedImage = result["image"] as! UIImage
let markerIds = result["markerIds"] as! [Int]
let resultsArray = result["resultsArray"] as! [[String: Any]]
DispatchQueue.main.async {
self.overlayImageView.image = processedImage
for markerId in markerIds {
if let cardButton = self.viewWithTag(markerId) as? UIButton {
cardButton.backgroundColor = .green
}
}
self.storeCardResult(resultsArray)
let cardResultCount = self.cardResult.count
let cardsCount = self.cardData.count
self.attendanceTotalCount.text = "\(cardResultCount) / \(cardsCount)"
}
}
}
}