Toggle

Hi, I am creating an app that recognises and extracts text from image.

The line of code request.recognitionLevel = .accurate can also take the value of request.recognitionLevel = .fast .

I would like to build a toggle which switches the value of request.recognitionLevel between fast and accurate however I am unsure on how to do this.

Any help would be greatly appreciated.

Many thanks for your time.

My code is shown below:

import SwiftUI

import UIKit

import Vision



class ViewController2: UIViewController {



    @IBOutlet weak var imageView: UIImageView!

    @IBOutlet weak var textView: UITextView!

    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!

    @IBOutlet weak var button: UIButton!

    

    override func viewDidLoad() {

        super.viewDidLoad()

        

        button.backgroundColor = .systemCyan // Background colour of the select photo button

        button.setTitle("Select Photo", for: .normal) // Button text

        button.setTitleColor(.white, for: .normal) // Button text Colour

        

        // Rounding the edges of the 'Select Photo' button:

        button.layer.cornerRadius = 25

        button.layer.borderWidth = 20

        button.layer.borderColor = UIColor.systemCyan.cgColor

        

        // Rounding the edges of the 'Share' button:

        shareButton.layer.cornerRadius = 25

        shareButton.layer.borderWidth = 10

        shareButton.layer.borderColor = UIColor.systemCyan.cgColor

        

        stopAnimating() // Activity indicator disappears

    }

    

    // Defining the activity indicators show and spin

    private func startAnimating() {

        self.activityIndicator.startAnimating()

    }

    

    // Defining the activity indicators stop and hide

    private func stopAnimating(){

        self.activityIndicator.stopAnimating()

    }



    

    @IBAction func selectPhotoButton(_ sender: Any) { // When selectPhoto button is pressed,

        SelectPhotoButtonPressed() // run the function SelectPhotoButtonPressed.

    }

    

    

    private func SelectPhotoButtonPressed(){

        

        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){

            

            let imageSelector = UIImagePickerController() // Apple's interface for taking pictures and loading items from camera roll

            imageSelector.sourceType = .photoLibrary // Opens the device's photo library

            imageSelector.delegate = self // Leaves the UIImagePickerController when a picture is selected

            self.present(imageSelector, animated: true, completion: nil) // Present the imageSelector

            

        }

    }

    

    

    // Text Recognition

    

    

    // Create request

    var request = VNRecognizeTextRequest(completionHandler: nil)

    

    private func VisionOCR(image: UIImage?){

        

        var textString = "" // Variable textString = string

        

        // Create completion handler

        request = VNRecognizeTextRequest(completionHandler: { (request, error)in  // Locates all the text in the image.

           

            // Results are in the request

            guard let results = request.results as?[VNRecognizedTextObservation] else {fatalError("Recieved Invalid Observation")}

            

            for visionResult in results{

                guard let recognisedText = visionResult.topCandidates(1).first else{ // Text stored in chronological order.

                    print("No text")

                    continue

                }

                

                textString += "\n\(recognisedText.string)"

                

                DispatchQueue.main.async{ // FIFO queue

                    self.stopAnimating() // Hide the activityIndicator

                    self.textView.text = textString // Assign the textView the recoginsed text

                }

                

            }

            

        })

        

        

        // Properties

        

        request.minimumTextHeight = 0.03125 // Default mimnimum height for text to be recognised in comparison to image, (1/32).

        request.recognitionLevel = .accurate // Choice between accurate and fast.

        request.recognitionLanguages = ["en_UK", "en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR", "zh-Hans", "zh-Hant", "yue-Hans", "yue-Hant", "ko-KR", "ja-JP", "ru-RU", "uk-UA"] // Recognisable languages.

        request.usesLanguageCorrection = true // Applies language correction.

        

        let requests = [request]

        

        

        // Request handler

        

        DispatchQueue.global(qos: .userInitiated).async{  // FIFO queue, qos (quality of service) determines priotity for scheduling tasks

            

            guard let img = image?.cgImage else {fatalError("Missing image to scan")} // Variable image = computer generated image

            

            // Create request handler

            let requestHandler = VNImageRequestHandler(cgImage: img, options: [:])  // Performs Vision requests

            

            // Send request to request handler

            try? requestHandler.perform(requests) // Schedules Vision requests to be performed

            

        }

        

    }

    

}





extension ViewController2: UIImagePickerControllerDelegate, UINavigationControllerDelegate{

    

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey :Any]){

        

        picker.dismiss(animated: true, completion: nil)

        

        startAnimating()

        self.textView.text = ""

        

        let image = info[UIImagePickerController.InfoKey.originalImage]as?UIImage

        self.imageView.image = image

        

        VisionOCR(image: image)

    }

}





public struct storyboardview2: UIViewControllerRepresentable{

    public func makeUIViewController(context content: Context) -> UIViewController {

        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

        let controller = storyboard.instantiateViewController(identifier: "selectPhoto")

        return controller

    }

    public func updateUIViewController(_ uiViewController: UIViewController, context: Context) {

        

    }

}

I would do the following.

create a State var accuracy (can be either .fast or .accurate)

In the button action, you set accuracy to the relevant value.

And replace

        request.recognitionLevel = .accurate // Choice between accurate and fast.

with

        request.recognitionLevel = accuracy

I have added a Pop Up Button to my storyboard and added the following to my code however now I am receiving an error stating 'Type of expression is ambiguous without more context'.

Any ideas would be greatly appreciated.

        func changeRecognitionLevel(_ sender: UIControl) {

            guard let selectedItem = sender.selectedItem else {

                return

            }

            switch selectedItem.identifier!.rawValue {

            case "fast":

                request.recognitionLevel = .fast

            default:

                request.recognitionLevel = .accurate

            }

        }

On which line exactly do you get the error ?

Try adding  VNRequestTextRecognitionLevel

        func changeRecognitionLevel(_ sender: UIControl) {

            guard let selectedItem = sender.selectedItem else {  return  }

            switch selectedItem.identifier!.rawValue {
                case "fast":
                    request.recognitionLevel = VNRequestTextRecognitionLevel.fast

                default:
                    request.recognitionLevel = VNRequestTextRecognitionLevel.accurate
            }

        }

Hi,

The error comes on the 2nd line guard let selectedItem = sender.selectedItem else { return }.

Many thanks for your help.

You declare sender as UIControl which has no selectedItem property.

        func changeRecognitionLevel(_ sender: UIControl) {

UIControl has no What is the precise type of the sender ?

You should either

  • change the func definition to with exact type
        func changeRecognitionLevel(_ sender: CorrectType) {
  • cast with
guard let selectedItem = (sender as? CorrectType).selectedItem

Hi,

I replaced the code as shown below. However, CorrectType is not defined. The error message states "Cannot find type 'CorrectType' in scope." How and where do I define it?

Many thanks for your help.

        func changeRecognitionLevel(_ sender: CorrectType) {



            guard let selectedItem = (sender as? CorrectType).selectedItem else {  return  }



            switch selectedItem.identifier!.rawValue {

                case "fast":

                    request.recognitionLevel = VNRequestTextRecognitionLevel.fast



                default:

                    request.recognitionLevel = VNRequestTextRecognitionLevel.accurate

            }



        }

I wrote CorrectType because you don't tell what type it is ! Of course, you must use the real type of the sender (the correct type 😆).

Could you show the code where you call changeRecognitionLevel ?

Hi, my code is shown below:

I have changed correct type to UIControl which I believe is the correct type however I still receive an error on the line guard let selectedItem = (sender as? UIControl).selectedItem else {  return  }

What I have on my storyboard is called a 'Pop Up Button' however I haven't/am unsure on how to define this button in the code. I have also created 2 elements on the storyboard in the pop up button called accurate and fast.

Many thanks for your help.

import SwiftUI
import UIKit
import Vision


class ViewController2: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var textView: UITextView!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var shareButton: UIButton!

    override func viewDidLoad() {

        super.viewDidLoad()

        button.backgroundColor = .systemCyan // Background colour of the select photo button
        button.setTitle("Select Photo", for: .normal) // Button text
        button.setTitleColor(.white, for: .normal) // Button text Colour

        

        // Rounding the edges of the 'Select Photo' button:
        button.layer.cornerRadius = 25
        button.layer.borderWidth = 20
        button.layer.borderColor = UIColor.systemCyan.cgColor
        

        // Rounding the edges of the 'Share' button:
        shareButton.layer.cornerRadius = 25
        shareButton.layer.borderWidth = 10
        shareButton.layer.borderColor = UIColor.systemCyan.cgColor
        

        stopAnimating() // Activity indicator disappears
    }


    // Defining the activity indicators show and spin
    private func startAnimating() {
        self.activityIndicator.startAnimating()
    } 

    // Defining the activity indicators stop and hide
    private func stopAnimating(){
        self.activityIndicator.stopAnimating()
    }

    @IBAction func selectPhotoButton(_ sender: Any) { // When selectPhoto button is pressed,
        SelectPhotoButtonPressed() // run the function SelectPhotoButtonPressed.
    }

    private func SelectPhotoButtonPressed(){
        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){

            let imageSelector = UIImagePickerController() // Apple's interface for taking pictures and loading items from camera roll
            imageSelector.sourceType = .photoLibrary // Opens the device's photo library
            imageSelector.delegate = self // Leaves the UIImagePickerController when a picture is selected
            self.present(imageSelector, animated: true, completion: nil) // Present the imageSelector
        }
    }
    

    // Text Recognition

    // Create request

    var request = VNRecognizeTextRequest(completionHandler: nil)
    private func VisionOCR(image: UIImage?){
        var textString = "" // Variable textString = string

        // Create completion handler
        request = VNRecognizeTextRequest(completionHandler: { (request, error)in  // Locates all the text in the image.

            // Results are in the request
            guard let results = request.results as?[VNRecognizedTextObservation] else {fatalError("Recieved Invalid Observation")}

            

            for visionResult in results{
                guard let recognisedText = visionResult.topCandidates(1).first else{ // Text stored in chronological order.
                    print("No text")
                    continue
                }

                textString += "\n\(recognisedText.string)"

                DispatchQueue.main.async{ // FIFO queue
                    self.stopAnimating() // Hide the activityIndicator
                    self.textView.text = textString // Assign the textView the recoginsed text
                }
            }
        })

        // Properties
        request.minimumTextHeight = 0.03125 // Default mimnimum height for text to be recognised in comparison to image, (1/32).
        request.recognitionLevel = .accurate // Choice between accurate and fast.
        var recognitionLevel: VNRequestTextRecognitionLevel = .accurate {

            didSet {
                VisionOCR(image: image)
            }
        }

        request.recognitionLanguages = ["en_UK", "en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR", "zh-Hans", "zh-Hant", "yue-Hans", "yue-Hant", "ko-KR", "ja-JP", "ru-RU", "uk-UA"] // Recognisable languages.

        request.usesLanguageCorrection = true // Applies language correction.

        let requests = [request]


        func changeRecognitionLevel(_ sender: UIControl) {

            guard let selectedItem = (sender as? UIControl).selectedItem else {  return  }

            switch selectedItem.identifier!.rawValue {

                case "fast":

                    request.recognitionLevel = VNRequestTextRecognitionLevel.fast

                default:

                    request.recognitionLevel = VNRequestTextRecognitionLevel.accurate
            }
        }


        // Request handler
        DispatchQueue.global(qos: .userInitiated).async{  // FIFO queue, qos (quality of service) determines priotity for scheduling tasks
            guard let img = image?.cgImage else {fatalError("Missing image to scan")} // Variable image = computer generated image


            // Create request handler
            let requestHandler = VNImageRequestHandler(cgImage: img, options: [:])  // Performs Vision requests


            // Send request to request handler
            try? requestHandler.perform(requests) // Schedules Vision requests to be performed 

        }
    }
}

extension ViewController2: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey :Any]){
        picker.dismiss(animated: true, completion: nil)
        startAnimating()
        self.textView.text = ""
        let image = info[UIImagePickerController.InfoKey.originalImage]as?UIImage
        self.imageView.image = image
        VisionOCR(image: image)
    }
}

public struct storyboardview2: UIViewControllerRepresentable{
    public func makeUIViewController(context content: Context) -> UIViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        let controller = storyboard.instantiateViewController(identifier: "selectPhoto")
        return controller
    }
    public func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }
}

You don't answer questions, so it is very hard to help.

  • Where do you USE changeRecognitionLevel ? Somewhere you must have (here, aControl is a generic name as I don't know what you use; we need to know how this aControl is declared)
changeRecognitionLevel(aControl)
  • UIControl is probably not the exact type. The sender is a subclass of UIControl, which has a selectedItem property. Which one is it ?

The change RecognitionLevel call is shown below.

    @IBAction func Accuracy(_ sender: Accuracy) {

        changeRecognitionLevel()

    }

I have therefore changed the function as shown below:

    func changeRecognitionLevel(_ sender: Accuracy) {
        guard let selectedItem = (sender as? Accuracy).selectedItem else {  return  }
        switch selectedItem.identifier!.rawValue {
            case "fast":
                request.recognitionLevel = VNRequestTextRecognitionLevel.fast
            default:
                request.recognitionLevel = VNRequestTextRecognitionLevel.accurate
        }
    }

As to your second question, I do not understand where I will find the selectedItem property.

Many thanks for your help.

I have rewritten the code as shown below, however there is still the same error:

The error comes on the second line of code and states 'Type of expression is ambiguous without more context'.

Any thoughts on how to fix this will be greatly appreciated.

    @IBAction func Accuracy(_ sender: UIMenuElement) {
        guard let selectedItem = sender.selectedItem else { return }
        switch selectedItem.identifier!.rawValue {
            case "fast":
                request.recognitionLevel = VNRequestTextRecognitionLevel.fast
            default:
                request.recognitionLevel = VNRequestTextRecognitionLevel.accurate
        }
    }

This doesn't answer the question…

What is Accuracy ? Is it a class of yours ? If so, please show.

I don't understand how this could compile:

    @IBAction func accuracy(_ sender: Accuracy) {  // Should be better to start with lowercase
        changeRecognitionLevel()
    }

changeRecognitionLevel expect a parameter …

Note: giving the same name to the func and the type is not the best idea. In addition, func name should start with lowercase, according to Swift practices.

.

I do not understand where I will find the selectedItem property.

You have to check if Accuracy clas has such a property.

No, accuracy is not a class, I just thought thatI had to give the action a name.

My code is shown below:

import SwiftUI
import UIKit
import Vision
class ViewController2: UIViewController {
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var textView: UITextView!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var shareButton: UIButton!
    @IBAction func accuracy(_ sender: UIEditMenuInteraction) {
        if let selectedItem = sender.menuItems.first?.selectedItem {
            switch selectedItem.identifier!.rawValue {
                case "fast":
                    request.recognitionLevel = VNRequestTextRecognitionLevel.fast
                default:
                    request.recognitionLevel = VNRequestTextRecognitionLevel.accurate
            }
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        button.backgroundColor = .systemCyan // Background colour of the select photo button
        button.setTitle("Select Photo", for: .normal) // Button text
        button.setTitleColor(.white, for: .normal) // Button text Colour
        // Rounding the edges of the 'Select Photo' button:
        button.layer.cornerRadius = 25
        button.layer.borderWidth = 20
        button.layer.borderColor = UIColor.systemCyan.cgColor
        // Rounding the edges of the 'Share' button:
        shareButton.layer.cornerRadius = 25
        shareButton.layer.borderWidth = 10
        shareButton.layer.borderColor = UIColor.systemCyan.cgColor
        stopAnimating() // Activity indicator disappears
    }
    // Defining the activity indicators show and spin
    private func startAnimating() {
        self.activityIndicator.startAnimating()

    }
    // Defining the activity indicators stop and hide
    private func stopAnimating(){
        self.activityIndicator.stopAnimating()
    }
    @IBAction func selectPhotoButton(_ sender: Any) { // When selectPhoto button is pressed,
        SelectPhotoButtonPressed() // run the function SelectPhotoButtonPressed.
    }
    private func SelectPhotoButtonPressed(){
        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
            let imageSelector = UIImagePickerController() // Apple's interface for taking pictures and loading items from camera roll
            imageSelector.sourceType = .photoLibrary // Opens the device's photo library
            imageSelector.delegate = self // Leaves the UIImagePickerController when a picture is selected
            self.present(imageSelector, animated: true, completion: nil) // Present the imageSelector
        }
    }
    // Text Recognition
    // Create request
    var request = VNRecognizeTextRequest(completionHandler: nil)
    private func VisionOCR(image: UIImage?){
        var textString = "" // Variable textString = string
        // Create completion handler
        request = VNRecognizeTextRequest(completionHandler: { (request, error)in  // Locates all the text in the image.
            // Results are in the request
            guard let results = request.results as?[VNRecognizedTextObservation] else {fatalError("Recieved Invalid Observation")}
            for visionResult in results{
                guard let recognisedText = visionResult.topCandidates(1).first else{ // Text stored in chronological order.
                    print("No text")
                    continue
                }
                textString += "\n\(recognisedText.string)"
                DispatchQueue.main.async{ // FIFO queue
                    self.stopAnimating() // Hide the activityIndicator
                    self.textView.text = textString // Assign the textView the recoginsed text
                }
            }
        })
        // Properties
        request.minimumTextHeight = 0.03125 // Default mimnimum height for text to be recognised in comparison to image, (1/32).
        request.recognitionLevel = .accurate // Choice between accurate and fast.
        request.recognitionLanguages = ["en_UK", "en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR", "zh-Hans", "zh-Hant", "yue-Hans", "yue-Hant", "ko-KR", "ja-JP", "ru-RU", "uk-UA"] // Recognisable languages.
        request.usesLanguageCorrection = true // Applies language correction.
        let requests = [request]
        //changeRecognitionLevel(Accuracy)
//        func changeRecognitionLevel(_ sender: Accuracy) {
//            guard let selectedItem = (sender as? Accuracy).selectedItem else {  return  }
//            switch selectedItem.identifier!.rawValue {
//                case "fast":
//                    request.recognitionLevel = VNRequestTextRecognitionLevel.fast
//                default:
//                    request.recognitionLevel = VNRequestTextRecognitionLevel.accurate
//            }
//        }
        // Request handler
        DispatchQueue.global(qos: .userInitiated).async{  // FIFO queue, qos (quality of service) determines priotity for scheduling tasks
            guard let img = image?.cgImage else {fatalError("Missing image to scan")} // Variable image = computer generated image
            // Create request handler
            let requestHandler = VNImageRequestHandler(cgImage: img, options: [:])  // Performs Vision requests
            // Send request to request handler
            try? requestHandler.perform(requests) // Schedules Vision requests to be performed
        }
    }
}
extension ViewController2: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey :Any]){
        picker.dismiss(animated: true, completion: nil)
        startAnimating()
        self.textView.text = ""
        let image = info[UIImagePickerController.InfoKey.originalImage]as?UIImage
        self.imageView.image = image
        VisionOCR(image: image)
    }
}
public struct storyboardview2: UIViewControllerRepresentable{
    public func makeUIViewController(context content: Context) -> UIViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        let controller = storyboard.instantiateViewController(identifier: "selectPhoto")
        return controller
    }
    public func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }
}

Just for your reference, the storyboard appears as shown below:

And the error message appears as shown below:

There are several things I do not understand in your code.

  • You defined an IBAction.
     @IBAction func accuracy(_ sender: UIEditMenuInteraction) { … }
  • To which object in the storyboard is it connected to ?

The way to define UIEditMenuInteraction is not with an IBAction, but

  • creating UIEditMenuInteraction in code

  • addInteraction to the object that will react to a longPress for instance.

  • create a longPress gestureRecognizer ; its selector will contain the code to execute on longPress

  • addGestureRecognizer to this object

  • conform your ViewController to UIEditMenuInteractionDelegate protocol

  • write a func editMenuInteraction that will create menus and manage their selection

See complete example in

  • Apple's doc (UIEditMenuInteraction in XCode documentation)

or

Toggle
 
 
Q