Cannot find 'VNDetectDocumentRectanglesRequest' in scope

203 Views Asked by At

I have been trying to copy some of the features of the Apple Notes App,i.e., the scanning feature. I have managed to implement almost everything. However, I can't just overcome this Error: Cannot find 'VNDetectDocumentRectanglesRequest' in scope. I have already added the Vision, VisionKit and CoreImage frameworks. I am targeting iOS 14 or newer.



import SwiftUI
import AVFoundation
import Vision
import VisionKit

struct TakingPhotoScreen: View {
    @Binding var isPhotoTaken: Bool
    @StateObject private var cameraViewModel = CameraViewModel()
    @State private var isProcessing = false
    @State private var showError = false
    @State private var showFlash = false
    @State private var showAutoCaptureGuide = true
    @State private var showManualAlignmentGuide = false
    @State private var detectedRectangle: CGRect?
    @State private var isDocumentAligned = false
    
    var body: some View {
        ZStack {
            Color.black.edgesIgnoringSafeArea(.all)
            
            VStack {
                HStack {
                    Spacer()
                    Text("Scan the Ballot")
                        .font(.custom("Helvetica Neue", size: 24))
                        .foregroundColor(.white)
                        .padding(.trailing)
                }
                
                // Camera preview layer
                CameraPreviewLayer(session: cameraViewModel.captureSession)
                    .overlay(
                        GeometryReader { geometry in
                            if let rectangle = detectedRectangle, !isDocumentAligned {
                                DocumentAlignmentOverlay(rectangle: rectangle, imageSize: geometry.size)
                                    .stroke(Color.green, lineWidth: 2)
                            }
                        }
                    )
                    .overlay(
                        VStack {
                            if showAutoCaptureGuide {
                                AutoCaptureGuide()
                            } else if showManualAlignmentGuide {
                                ManualAlignmentGuide(isDocumentAligned: $isDocumentAligned)
                            }
                        }
                    )
                
                Button(action: {
                    capturePhoto()
                }) {
                    Text("Capture Photo")
                        .font(.custom("Helvetica Neue Bold", size: 18))
                        .foregroundColor(.white)
                        .padding()
                        .background(Color.blue)
                        .cornerRadius(10)
                        .disabled(isProcessing)
                }
                .padding()
                
                Button(action: {
                    toggleFlash()
                }) {
                    Image(systemName: showFlash ? "bolt.fill" : "bolt.slash.fill")
                        .font(.system(size: 24))
                        .foregroundColor(showFlash ? .yellow : .white)
                }
                .padding(.bottom, 20)
                
                if showError {
                    Text("Error capturing photo. Please try again.")
                        .font(.custom("Helvetica Neue", size: 16))
                        .foregroundColor(.red)
                        .padding(.bottom, 20)
                }
            }
        }
        .edgesIgnoringSafeArea(.all)
        .onAppear {
            cameraViewModel.startSession()
        }
        .onDisappear {
            cameraViewModel.stopSession()
        }
    }
    
    func capturePhoto() {
        isProcessing = true
        showError = false
        
        cameraViewModel.capturePhoto { image in
            if let image = image {
                processImage(image)
            } else {
                showError = true
                isProcessing = false
            }
        }
    }
    
    func processImage(_ image: UIImage?) {
        guard let image = image else {
            showError = true
            isProcessing = false
            return
        }
        
        // Convert UIImage to CIImage
        guard let ciImage = CIImage(image: image) else {
            showError = true
            isProcessing = false
            return
        }
        
        // Perform document scanning using Vision and Core Image
        let handler = VNImageRequestHandler(ciImage: ciImage, orientation: .up)
        
        do {
            let documentScanRequest = VNDetectDocumentRectanglesRequest { request, error in
                if let error = error {
                    print("Error detecting document rectangle: \(error)")
                    self.showError = true
                    self.isProcessing = false
                    return
                }
                
                guard let observations = request.results as? [VNRectangleObservation],
                      let detectedRectangle = observations.first?.boundingBox else {
                    print("No document rectangle detected.")
                    self.showError = true
                    self.isProcessing = false
                    return
                }
                
                self.detectedRectangle = detectedRectangle
                
                if self.isDocumentAligned {
                    // Crop the document from the image using the detected rectangle
                    let croppedImage = ciImage.cropped(to: detectedRectangle)
                    
                    // Perform further processing or analysis with the cropped image
                    
                    // Set the processed result or pass it to the next screen
                    DispatchQueue.main.async {
                        // Example code to pass the processed image to the next screen
                        // Replace with your own logic or data flow
                        let processedImage = UIImage(ciImage: croppedImage)
                        cameraViewModel.processedPhoto = processedImage
                        isPhotoTaken = true
                        isProcessing = false
                    }
                } else {
                    showManualAlignmentGuide = true
                    showAutoCaptureGuide = false
                    isProcessing = false
                }
            }
            
            try handler.perform([documentScanRequest])
        } catch {
            print("Error processing document scan request: \(error)")
            showError = true
            isProcessing = false
        }
    }
    
    func toggleFlash() {
        cameraViewModel.toggleFlash { success in
            if success {
                showFlash.toggle()
            }
        }
    }
}

struct CameraPreviewLayer: UIViewRepresentable {
    var session: AVCaptureSession
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        let layer = AVCaptureVideoPreviewLayer(session: session)
        layer.videoGravity = .resizeAspectFill
        layer.frame = view.bounds
        view.layer.addSublayer(layer)
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        uiView.frame = UIScreen.main.bounds
    }
}


struct DocumentAlignmentOverlay: Shape {
    var rectangle: CGRect
    var imageSize: CGSize
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        let scaleX = rect.width / imageSize.width
        let scaleY = rect.height / imageSize.height
        
        let transformedRect = rectangle.applying(CGAffineTransform(scaleX: scaleX, y: scaleY))
        
        path.addRect(transformedRect)
        
        return path
    }
}

struct ManualAlignmentGuide: View {
    @Binding var isDocumentAligned: Bool
    
    var body: some View {
        VStack {
            Image(systemName: "hand.draw.fill")
                .font(.system(size: 30))
                .foregroundColor(.white)
                .padding(.bottom, 10)
            
            Text("Adjust the alignment for accurate scanning.")
                .font(.custom("Helvetica Neue", size: 16))
                .foregroundColor(.white)
                .padding(.bottom, 20)
            
            Button(action: {
                isDocumentAligned = true
            }) {
                Text("Continue")
                    .font(.custom("Helvetica Neue Bold", size: 18))
                    .foregroundColor(.white)
                    .padding()
                    .background(Color.blue)
                    .cornerRadius(10)
            }
        }
    }
}

class CameraViewModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {
    public let captureSession = AVCaptureSession()
    private let photoOutput = AVCapturePhotoOutput()
    
    @Published var processedPhoto: UIImage?
    @Published var isFlashEnabled = false
    
    override init() {
        super.init()
        setupCaptureSession()
    }
    
    func startSession() {
        captureSession.startRunning()
    }
    
    func stopSession() {
        captureSession.stopRunning()
    }
    
    func capturePhoto(completion: @escaping (UIImage?) -> Void) {
        DispatchQueue.global(qos: .userInitiated).async {
            let settings = AVCapturePhotoSettings()
            
            if self.isFlashEnabled {
                settings.flashMode = .on
            }
            
            self.photoOutput.capturePhoto(with: settings, delegate: self)
            
            DispatchQueue.main.async {
                completion(self.processedPhoto)
            }
        }
    }
    
    func toggleFlash(completion: @escaping (Bool) -> Void) {
        guard let device = AVCaptureDevice.default(for: .video),
              device.hasTorch else {
            completion(false)
            return
        }
        
        do {
            try device.lockForConfiguration()
            
            if device.torchMode == .on {
                device.torchMode = .off
                isFlashEnabled = false
            } else {
                try device.setTorchModeOn(level: AVCaptureDevice.maxAvailableTorchLevel)
                isFlashEnabled = true
            }
            
            device.unlockForConfiguration()
            completion(true)
        } catch {
            print("Error toggling flash: \(error)")
            completion(false)
        }
    }
    
    private func setupCaptureSession() {
        guard let device = AVCaptureDevice.default(for: .video),
              let input = try? AVCaptureDeviceInput(device: device) else {
            return
        }
        
        if captureSession.canAddInput(input) {
            captureSession.addInput(input)
        }
        
        if captureSession.canAddOutput(photoOutput) {
            captureSession.addOutput(photoOutput)
        }
    }
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard let imageData = photo.fileDataRepresentation(),
              let image = UIImage(data: imageData) else {
            processedPhoto = nil
            return
        }
        
        processedPhoto = image
    }
}

struct AutoCaptureGuide: View {
    var body: some View {
        VStack {
            Image(systemName: "camera.metering.center.weighted")
                .font(.system(size: 40))
                .foregroundColor(.white)
                .padding(.bottom, 10)
            
            Text("Position the document within the frame.")
                .font(.custom("Helvetica Neue", size: 16))
                .foregroundColor(.white)
                .padding(.bottom, 20)
            
            Image(systemName: "hand.point.up.fill")
                .font(.system(size: 30))
                .foregroundColor(.white)
        }
    }
}

struct ContentView: View {
    @State private var isPhotoTaken = false
    
    var body: some View {
        if isPhotoTaken {
            // Add code for the confirmation screen or other views
            Text("Confirmation Screen")
        } else {
            TakingPhotoScreen(isPhotoTaken: $isPhotoTaken)
        }
    }
}

0

There are 0 best solutions below