Text Recognition in Images in Swift - ios

Im new to swift and I was trying to make an app that can parse the from on a screenshot. I have the following code so far, and I wasnt able to figure out proper way to call the recognize function in my ContentView, any help is appreciated:
`
struct ContentView: View {
#State var selectedItems: PhotosPickerItem?
#State var selectedPhotoData: Data?
func recogText(selData: Data?)
{
if let selData = selData, let image = UIImage(data: selData){
guard let cgImage = image.cgImage else {return}
let handler = VNImageRequestHandler(cgImage: cgImage)
let request = VNDetectTextRectanglesRequest { request, error in
guard let observations = request.results as? [VNRecognizedTextObservation],
error == nil else {return}
let text = observations.compactMap({
$0.topCandidates(1).first?.string
}).joined(separator: ", ")
print(text.count)
}
do {
try handler.perform([request])
}
catch {
print("Unable to perform the requests: \(error).")
}
}
}
var body: some View {
VStack{
//Icon
mainImage()
//Button
PhotosPicker(selection: $selectedItems, matching: .images) {
Label("Select a photo", systemImage: "photo")
}
.tint(.blue)
.controlSize(.large)
.buttonStyle(.borderedProminent)
.onChange(of: selectedItems) { newItem in
Task {
if let data = try? await newItem?.loadTransferable(type: Data.self) {
selectedPhotoData = data
let _ = recogText(selData: data)
}
}
}
}
}
}
`
Expected a print of the parsed text but no output was found

Hello here is an example function that might help you. Of course you have to replace the TestImage with yours. This might work for you and you need to import Vision
func recogText() {
let textRecognitionRequest = VNRecognizeTextRequest { (request, error) in
// Insert code to process the text recognition results here
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
for observation in observations {
let topCandidate = observation.topCandidates(1).first
if let recognizedText = topCandidate?.string {
print(recognizedText)
}
}
}
textRecognitionRequest.recognitionLevel = .accurate
let image = UIImage(named: "TestImage")
let imageRequestHandler = VNImageRequestHandler(cgImage: (image?.cgImage!)!, options: [:])
do {
try imageRequestHandler.perform([textRecognitionRequest])
} catch {
print(error)
}
}

Related

How to refactor duplicate Firestore document IDs in Swift?

I'm doing my very first IOS app using Cloud Firestore and have to make the same queries to my database repeatedly. I would like to get rid of the duplicate lines of code. This is examples of func where documents ID are duplicated. Also I using other queries as .delete(), .addSnapshotListener(), .setData(). Should I refactor all that queries somehow or leave them because they were used just for one time?
#objc func updateUI() {
inputTranslate.text = ""
inputTranslate.backgroundColor = UIColor.clear
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { [self] (document, error) in
if let document = document, document.exists {
let document = document
let label = document.data()?.keys.randomElement()!
self.someNewWord.text = label
// Fit the label into screen
self.someNewWord.adjustsFontSizeToFitWidth = true
self.checkButton.isHidden = false
self.inputTranslate.isHidden = false
self.deleteBtn.isHidden = false
} else {
self.checkButton.isHidden = true
self.inputTranslate.isHidden = true
self.deleteBtn.isHidden = true
self.someNewWord.adjustsFontSizeToFitWidth = true
self.someNewWord.text = "Add your first word to translate"
updateUI()
}
}
}
#IBAction func checkButton(_ sender: UIButton) {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { (document, error) in
let document = document
let label = self.someNewWord.text!
let currentTranslate = document!.get(label) as? String
let translateField = self.inputTranslate.text!.lowercased().trimmingCharacters(in: .whitespaces)
if translateField == currentTranslate {
self.inputTranslate.backgroundColor = UIColor.green
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
self.inputTranslate.backgroundColor = UIColor.clear
updateUI()}
} else {
self.inputTranslate.backgroundColor = UIColor.red
self.inputTranslate.shakingAndRedBg()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
self.inputTranslate.backgroundColor = UIColor.clear
self.inputTranslate.text = ""
}
}
}
}
func deletCurrentWord () {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { (document, err) in
let document = document
if let err = err {
print("Error getting documents: \(err)")
} else {
let array = document!.data()
let counter = array!.count
if counter == 1 {
// The whole document will deleted together with a last word in list.
let user = Auth.auth().currentUser?.email
self.db.collection(K.FStore.collectionName).document(user!).delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
self.updateUI()
}
}
} else {
// A current word will be deleted
let user = Auth.auth().currentUser?.email
let wordForDelete = self.someNewWord.text!
self.db.collection(K.FStore.collectionName).document(user!).updateData([
wordForDelete: FieldValue.delete()
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
self.updateUI()
}
}
}
}
}
}
Another query example
func loadMessages() {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.addSnapshotListener { (querySnapshot, error) in
self.messages = []
if let e = error {
print(e)
} else {
if let snapshotDocuments = querySnapshot?.data(){
for item in snapshotDocuments {
if let key = item.key as? String, let translate = item.value as? String {
let newMessage = Message(key: key, value: translate)
self.messages.append(newMessage)
}
}
DispatchQueue.main.async {
self.messages.sort(by: {$0.value > $1.value})
self.secondTableView.reloadData()
let indexPath = IndexPath(row: self.messages.count - 1, section: 0)
self.secondTableView.scrollToRow(at: indexPath, at: .top, animated: false)
}
}
}
}
}
}
enum Error {
case invalidUser
case noDocumentFound
}
func fetchDocument(onError: #escaping (Error) -> (), completion: #escaping (FIRQueryDocument) -> ()) {
guard let user = Auth.auth().currentUser?.email else {
onError(.invalidUser)
return
}
db.collection(K.FStore.collectionName).document(user).getDocument { (document, error) in
if let error = error {
onError(.noDocumentFound)
} else {
completion(document)
}
}
}
func updateUI() {
fetchDocument { [weak self] error in
self?.hideShowViews(shouldHide: true, newWordText: nil)
} completion: { [weak self] document in
guard document.exists else {
self?.hideShowViews(shouldHide: true, newWordText: nil)
return
}
self?.hideShowViews(shouldHide: false, newWordText: document.data()?.keys.randomElement())
}
}
private func hideShowViews(shouldHide: Bool, newWordText: String?) {
checkButton.isHidden = shouldHide
inputTranslate.isHidden = shouldHide
deleteBtn.isHidden = shouldHide
someNewWord.adjustsFontSizeToFitWidth = true
someNewWord.text = newWordText ?? "Add your first word to translate"
}
The updateUI method can easily be refactored using a simple guard statement and then taking out the common code into a separate function. I also used [weak self] so that no memory leaks or retain cycles occur.
Now, you can follow the similar approach for rest of the methods.
Use guard let instead of if let to avoid nesting.
Use [weak self] for async calls to avoid memory leaks.
Take out the common code into a separate method and use a Bool flag to hide/show views.
Update for step 3:
You can create methods similar to async APIs for getDocument() or delete() etc and on completion you can update UI or perform any action. You can also create a separate class and move the fetchDocument() and other similar methods in there and use them.

How to iterate over a Custom object with asyncsequence?

Hi i am getting following errors while iterating over a custom object with for try await:-
For-in loop requires '[Photo]' to conform to 'AsyncSequence'
Type of expression is ambiguous without more context
Custom object:-
enum FetchError: Error {
case badImage
case badRequest
case invalidImageURL
case noURL
case failedToFetchImage
}
struct Photo: Codable {
let albumId: Int
let id: Int
let title: String
let urlPath: String
let thumbnailUrl: String
}
Working code for fetching 1st image:-
Class ViewController: UIViewController {
func fetchAsyncImage(request:URLRequest) async throws -> UIImage {
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
let photos = try JSONDecoder().decode([Photo].self, from: data)
guard let imagePath = photos.first?.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let firstImage = UIImage(data: imageData) else { throw FetchError.badImage }
return firstImage
}
Issue while performing async sequence on Photo object
func fetchAsyncImage(request:URLRequest) async throws -> [UIImage] {
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
let photos = try JSONDecoder().decode([Photo].self, from: data)
guard let imagePath = photos.first?.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
var imageArr:[UIImage] = []
for await photo in photos {
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
imageArr.append(image)
} catch {
throw FetchError.failedToFetchImage
}
}
return imageArr
}
Getting this error: -
What i tried for implementing async sequence:-
struct Photo: Codable, AsyncSequence {
typealias Element = URL
let albumId: Int
let id: Int
let title: String
let urlPath: String
let thumbnailUrl: String
struct AsyncIterator: AsyncIteratorProtocol {
let urlPath: String
mutating func next() async throws -> URL? {
do {
guard let imageURL = URL.init(string: urlPath) else { throw FetchError.noURL }
return imageURL
} catch {
throw FetchError.invalidImageURL
}
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator(urlPath: urlPath)
}
}
i am not sure how to iterate over photo objects with "for try await"
photos is not an async sequence. An async sequence is a sequence that returns its next elements asynchronously. However, photos is an array of Photos, an array, everything is stored in memory. To fetch the next element, you just access the next thing in memory. There's nothing async about it. In this case, it is the processing (fetching the UIImage) of the array elements that involves an asynchronous operation, not the “fetching the next element” part, so you should use await in the loop body, which you correctly did.
A regular for loop will do the job:
for photo in photos {
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
imageArr.append(image)
} catch {
throw FetchError.failedToFetchImage
}
}
The loop will fetch the first image, wait for it to finish, then fetch the second, wait for that to finish, and so on. IMO, you could just fetch them all at the same time with a task group, unless you have some special requirements that I'm not aware of.
Anyway, it is also incorrect to conform Photo to AsyncSequence. After all, one Photo isn't a sequence. What you can do instead, if you really want to use AsyncSequence, is to create a AsyncPhotoSequence struct that takes a [Photo]. Note that this is a sequence of UIImage.
struct AsyncPhotoSequence: AsyncSequence {
let photos: [Photo]
typealias Element = UIImage
struct AsyncPhotoIterator: AsyncIteratorProtocol {
var arrayIterator: IndexingIterator<[Photo]>
mutating func next() async throws -> UIImage? {
guard let photo = arrayIterator.next() else { return nil }
// Here you are supposed to check for cancellation,
// read more here:
// https://developer.apple.com/documentation/swift/asynciteratorprotocol#3840267
// copied from your loop body
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
return image
} catch {
throw FetchError.failedToFetchImage
}
}
}
func makeAsyncIterator() -> AsyncPhotoIterator {
AsyncPhotoIterator(arrayIterator: photos.makeIterator())
}
}
Then you can use for try await with AsyncPhotoSequence:
for try await image in AsyncPhotoSequence(photos: photos) {
imageArr.append(image)
}
But I personally wouldn't go to that trouble.

AppGroup and Widget extension

I have a barcode generator app where you generate barcode and you can add to the related widget.
When you add to the widget, I want to add image and text.
For image is ok but I have problem with text under the image
I have a File Manager swift file which is:
import Foundation
extension FileManager {
static let appGroupContainerURL = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.com.antoniocristiano.BarWidget")!`
static func clearAllFile() {
let fileManager = FileManager.default
let myDocuments = FileManager.appGroupContainerURL.appendingPathComponent(FileManager.qrCode)
do {
try fileManager.removeItem(at: myDocuments)
} catch {
return
}
}
}
extension FileManager {
static let qrCode = "qrCode.png"
}
extension String {
var isValidURL: Bool {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
if let match = detector.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) {
// it is a link, if the match covers the whole string
return match.range.length == self.utf16.count
} else {
return false
}
}
}
And the code of the widget is:
var body: some View {
VStack {
imageFromFile
.resizable()
.scaledToFit()
Text("\(luckyNumberFromFile)")
}
}
var imageFromFile: Image {
let path = FileManager.appGroupContainerURL.appendingPathComponent(FileManager.qrCode).path
let img = UIImage(contentsOfFile: path) ?? UIImage(named: "qr")!
return Image(uiImage: img)
}
var luckyNumberFromFile: Int {
let url = FileManager.appGroupContainerURL.appendingPathComponent(FileManager.qrCode)
guard let text = try? String(contentsOf: url, encoding: .utf8) else { return 0 }
return Int(text) ?? 0
}
The image will update but not the text
Thank you very much for your help
Best

Cannot return String from function

I've tried to return String from my function, but I get error "Use of unresolved identifier nameOfFlower". Here's my function:
func detectFlower(image: CIImage) -> String {
guard let model = try? VNCoreMLModel(for: FlowerModels().model) else {
fatalError("Cannot import a model.")
}
let request = VNCoreMLRequest(model: model) { (request, error) in
let classification = request.results?.first as? VNClassificationObservation
var nameOfFlower = String(classification?.identifier ?? "Unexpected type")
}
let handler = VNImageRequestHandler(ciImage: image)
do {
try handler.perform([request])
} catch {
print(error)
}
return nameOfFlower
}
What is wrong with code?
Its async code .. so use closure as completion block
func detectFlower(image: CIImage,completion: #escaping (_ getString:String?,_ error:Error?)-> Void) {
guard let model = try? VNCoreMLModel(for: FlowerModels().model) else {
fatalError("Cannot import a model.")
}
let request = VNCoreMLRequest(model: model) { (request, error) in
let classification = request.results?.first as? VNClassificationObservation
var nameOfFlower = String(classification?.identifier ?? "Unexpected type")
completion(nameOfFlower,nil)
}
let handler = VNImageRequestHandler(ciImage: image)
do {
try handler.perform([request])
} catch {
print(error)
completion(nil,error)
}
}
How to use
detectFlower(image: yourImage) { (flowerString, error) in
// you get optional flower string here
}

Getting Image from URL and append it to array

I am trying to get images from a URL and append them to an array. This if my function for it:
func doStuff(html: String?){
do {
let doc: Document = try SwiftSoup.parse(html ?? "")
let priceClasses: Elements? = try doc.select("[class~=(?i)price]")
for priceClass: Element in priceClasses!.array() {
let priceText : String = try priceClass.text()
print(try priceClass.className())
print("pricetext: \(priceText)")
}
let srcs: Elements = try doc.select("img[src]")
let srcsStringArray: [String?] = srcs.array().map { try? $0.attr("src").description }
for imageName in srcsStringArray {
if (imageName?.matches("^https?://(?:[a-z0-9\\-]+\\.)+[a-z]{2,6}(?:/[^/#?]+)+\\.(?:jpg|gif|png)$"))! {
print(imageName!)
let imageView = UIImageView()
imageView.downloaded(from: imageName!) {
if let image = imageView.image {
self.imagesArray!.append(image)
} else {
print("Image '\(String(describing: imageName))' does not exist!")
}
}
}
}
} catch Exception.Error( _, let message) {
print(message)
} catch {
print("error")
}
}
This code is not working as it always exits and print <imageName> does not exist! . The weird thing is that the fileName is a correct name!
For example:
https://www.adidas.de/on/demandware.static/-/Sites-adidas-DE-Library/default/dw817801e3/Originals_Brand_Nav_Title.png
This is how I download the image from the URL:
extension UIImageView {
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() {
self.image = image
}
}.resume()
}
func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, finished: () -> Void) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode)
finished()
}
}
Does anyone have any idea why I can not append images to my array ??? Im stuck..
I fixed the issue by changing the way I load the image:
extension UIImage {
public static func loadFrom(url: URL, completion: #escaping (_ image: UIImage?) -> ()) {
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
DispatchQueue.main.async {
completion(UIImage(data: data))
}
} else {
DispatchQueue.main.async {
completion(nil)
}
}
}
}
}
With this I simply call:
for imageName in srcsStringArray {
if (imageName?.matches("^https?://(?:[a-z0-9\\-]+\\.)+[a-z]{2,6}(?:/[^/#?]+)+\\.(?:jpg|gif|png)$"))! {
guard let url = URL(string: imageName!) else { return }
UIImage.loadFrom(url: url) { image in
if let image = image {
print("append")
self.imagesArray.append(image)
} else {
print("Image '\(String(describing: imageName))' does not exist!")
}
}
}
}

Resources