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) {

        

    }

}

Hi, thanks for your reply but I don't particularly want an object to interact on a long press. What I am after is something that will look like this:

And will interact with the user on tap like this:

The problem js I josh to not know how to implement something like this.

he images I have uploaded are of a NSPopUpButton however I believe they are only used in Mac OS and so I can't use them in my iOS app.

Any thoughts on how I could implement something like this would be great.

Many thanks

I have rewritten the code for my button as shown below. It no longer crashes however I can not see the button / it is not visible on the simulator. Any thoughts would be great. Many thanks

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.
        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]
![]("https://developer.apple.com/forums/content/attachment/802b997e-3c1d-400a-ad6a-a914a23599f7" "title=Simulator Screen Shot - iPhone 14 Pro Max - 2022-12-13 at 12.39.54.png;width=1290;height=2796")

        // MARK: - Pop-up Button
        let AccurateRecognition = { (action: UIAction) in
            print(action.title) // do color change job
            self.request.recognitionLevel = VNRequestTextRecognitionLevel.fast
        }
        let FastRecognition = { (action: UIAction) in
            print(action.title) // do color change job
            self.request.recognitionLevel = VNRequestTextRecognitionLevel.fast
        }
        lazy var popupButton: UIButton = {
            let button = UIButton(primaryAction: nil)
            button.menu = UIMenu(children: [
                UIAction(title: "Accurate", handler: AccurateRecognition),
                UIAction(title: "Fast", state: .on, handler: FastRecognition),
            ])
            button.showsMenuAsPrimaryAction = true
            button.changesSelectionAsPrimaryAction = true // now its a pop-up button
            return button
        }()
         func accessPopupButtonProperties() {
            // Update to the currently set one
            print(popupButton.menu?.selectedElements.first?.title ?? "") // get currenty set
            // Update the selection
            (popupButton.menu?.children[0] as? UIAction)?.state = .on
        }
        // 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) {
    }
}

OK, then you can do something very simple.

Use a segmentedControl with 2 items.

You may have an "Accuracy" button which sole action will be to show the segmentedControl. Once selection done, hide the segmentedControl.

In the image below, you can see that I have a file called SettingsTableViewCell which tries to access a variable called request.recognitionlevel.

In the image below, you can see that I have another file called ViewController2, which defines and uses the variable request.recognition level.

How can I make the variable request.recognition visible in the SettingsTableViewCell, so that's its value in ViewController2 can be changed from the button in SettingsTableView?

If I understand, you want to change a var defined in ViewController4 from ViewController2.

You have several options:

  • define a global var request. Not very clean.
  • if ViewController 2 and 4 are loaded, post a notification from controller4 when you change request. And have controller2 listen to such notification to change the accuracy. Problem if VC4 is not loaded when you post: notification may be lost.
  • Use delegation. That is the cleanest way.

In the old post I desxcribed the mechanism if you need it: https://forums.developer.apple.com/thread/111569

It is speaking of textFields, but you can easily apply to accuracy:


You have VC1 and VC2 ; you want to update a field in VC1 when you change a text in VC2.

Declare a protocol protocol UpdateVC1 { func updateName(newName: String) }

Declare that VC1 conforms to it, and implement it: Set the delegate property of VC2 when you segue class VC1: UIViewController, UpdateVC1 { @IBOutlet weak var userNameField: UITextField!

func updateName(newName: String) {
    userNameField.text = newName
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? VC2 {
        destination.delegate = self
    }
}

}

In VC2 declare a delegate property: class VC2: UIViewController {

var delegate: UpdateVC1?

Then, when you change the field with name, invoke the delegate

    delegate?.updateName(newName: name)

So, I have added the code shown below in my ViewController2, which defines the variable.

        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if let destination = segue.destination as? ViewController4 {
                destination.delegate = self
            }
        }
        func updatetoAccurate(newName: String) {
            request.recognitionLevel = .accurate // Choice between accurate and fast.
        }
        override func prepare2(for segue: UIStoryboardSegue, sender: Any?) {
            if let destination = segue.destination as? ViewController4 {
                destination.delegate = self
            }
        }

And I have added the code below in my ViewController4, which attempts to change the value of the variable:

    let AccurateRecognition = { (action: UIAction) in
        print("Accurate")
        print(action.title) // do color change job
        //request.recognitionLevel = .accurate // Choice between accurate and fast.
        var delegate: UpdateViewController2?
        delegate?.updatetoFast(newName: name)
    }
    let FastRecognition = { (action: UIAction) in
        print("Fast")
        print(action.title) // do color change job
        //request.recognitionLevel = .fast // Choice between accurate and fast.
        var delegate: UpdateViewController2?
        delegate?.updatetoAccurate(newName: name)
    }

However, I still receive multiple errors as shown in the photos below.

ViewController2:

ViewController4:

Waouh, I think you got confused by my explanation when you tried to apply.

The principle is:

  • You declare a protocol
protocol UpdateVC4 {
   func updateAccuracy(newAcc: VNRequestTextRecognitionLevel)
}
  • Declare that VC4 conforms to it,
class ViewController4 : UIViewController, UpdateVC4 {
  • and implement the protocol in VC4:
func updateAccuracy(newAcc: VNRequestTextRecognitionLevel) {
    // Set accuracy value here
}
  • In VC2 declare a delegate property:
class ViewController2: UIViewController {
    var delegate: UpdateVC4?
  • Then, when you change the accuracy in VC2 with newAccuracyValue, invoke the delegate
    delegate?.updateAccuracy(newAcc: newAccuracyValue)  // Note: use the right var, newAccuracyValue is just a name I invented here
  • When you transition from VC4 to VC2, set the delegate property of VC2
  • do you use a segue ? if so, you do it in prepare
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? VC2 {
        destination.delegate = self
    }
}

If that doesn't work or you miss something, please post the complete code of ViewController2 and ViewController4 so that we can tell exactly how to implement.

Below I have attached the code for my ViewController4 and a screenshot of the error.

Any thought in how to fix this would be great.

import UIKit
import SwiftUI
class ViewController4: UIViewController, UpdateVC4 {
    let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.alignment = .center
        stack.spacing = 20
        return stack
    }()

    protocol UpdateVC4 {
       func updateAccuracy(newAcc: VNRequestTextRecognitionLevel)
    }

    func updateAccuracy(newAcc: VNRequestTextRecognitionLevel) {
        // Set accuracy value here
        request.recognitionLevel = .fast // Choice between accurate and fast.
    }

    lazy public var popupButton: UIButton = {
        let button = UIButton(primaryAction: nil)
        button.menu = UIMenu(children: [
            UIAction(title: "Accurate Recignition", state: .on, handler: updateAccuracy),
            UIAction(title: "Fast Recognition",handler: updateAccuracy),
        ])
        button.showsMenuAsPrimaryAction = true
        button.changesSelectionAsPrimaryAction = true // now its a pop-up button
        return button
    }()
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    fileprivate func accessPopupButtonProperties() {
        // Update to the currently set one
        print(popupButton.menu?.selectedElements.first?.title ?? "") // get currenty set
        // Update the selection
        (popupButton.menu?.children[0] as? UIAction)?.state = .on
    }
    fileprivate func setupUI() {
        view.backgroundColor = .systemBackground
        stackView.translatesAutoresizingMaskIntoConstraints = false
        popupButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        stackView.addArrangedSubview(popupButton)
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),
            stackView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 100),
            stackView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -100),
            stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
    }
}
public struct storyboardview4: UIViewControllerRepresentable{
    public func makeUIViewController(context content: Context) -> UIViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
        let controller = storyboard.instantiateViewController(identifier: "Settings")
        return controller
    }
    public func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }
}

Toggle
 
 
Q