Swift access progressvalue in VNRecognizeTextRequest with completion handler - ios

I'd like to capture the progress value in a VNRecognizeTextRequest session. So I inclueded it in a closure. The problem is it is passed when the closure is completed. I can capture the value and print it but not pass it to the main thread to update my progress bar. So I pass from 0% to 100% in the main thread. Can anybody give me a hand please? Thanks a lot.
Here's my code.
private func readImage(image:UIImage, completionHandler:#escaping(([VNRecognizedText]?,Error?)->Void), comp:#escaping((Double?,Error?)->())) {
var recognizedTexts = [VNRecognizedText]()
let requestHandler = VNImageRequestHandler(cgImage: (image.cgImage)!, options: [:])
let textRequest = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation] else { completionHandler(nil,error)
return
}
for currentObservation in observations {
let topCandidate = currentObservation.topCandidates(1)
if let recognizedText = topCandidate.first {
recognizedTexts.append(recognizedText)
}
}
completionHandler(recognizedTexts,nil)
}
textRequest.recognitionLevel = .accurate
textRequest.recognitionLanguages = ["es"]
textRequest.usesLanguageCorrection = true
textRequest.progressHandler = {(request, value, error) in
print(value)
comp(value,nil)
}
try? requestHandler.perform([textRequest])
}
This is how I call my function from the content view.
struct ContentView: View {
#State var ima = drawPDFfromURL(url: dalai)
#State private var stepperCounter = 0
#State private var observations = [VNRecognizedText]()
#State private var progressValue: Float = 0.0
private var originaImage = drawPDFfromURL(url: dalai)
var body: some View {
VStack { Button(action: {
//self.observations = readText(image: self.ima!)
DispatchQueue.main.async {
readImage(image: self.ima!, completionHandler: { (texts, error) in
self.observations = texts!
}) { (value, err) in
self.progressValue = Float(value!)
}
}
})
{
Text("Read invoice")
}
ProgressBar(value: $progressValue).frame(height: 20)
}.padding()
}
}
This is my ProgressBar object
struct ProgressBar: View {
#Binding var value: Float
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle().frame(width: geometry.size.width , height: geometry.size.height)
.opacity(0.3)
.foregroundColor(Color(UIColor.systemTeal))
Rectangle().frame(width: min(CGFloat(self.value)*geometry.size.width, geometry.size.width), height: geometry.size.height)
.foregroundColor(Color(UIColor.systemBlue))
.animation(.linear)
}.cornerRadius(45)
}
}
}

I believe that you need to put your request in a async Thread.
DispatchQueue.global(qos: .default).async {
// Run request
perform(...)
}

Related

how to make A SwiftUI Image Gallery/Slideshow With Auto Scrolling in SwiftUI?

How can I make a slider when my Data is coming from API? I am using
this(below code) for static images work fine but whenever I try to
use API data then my code does not work.
How to Set the Marquee in this images.
This is My code
public struct MagazineModel: Decodable {
public let magzineBanners: [MagzineBanner]
}
public struct MagzineBanner: Decodable, Identifiable {
public let id: Int
public let url: String
}
This is My View Model
//View Model for Magazines and showing Details
class MagazineBannerVM: ObservableObject{
#Published var datas = [MagzineBanner]()
let url = "ApiUrl"
init() {
getData(url: url)
}
func getData(url: String) {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let results = try JSONDecoder().decode(MagazineModel.self, from: data)
DispatchQueue.main.async {
self.datas = results.magzineBanners
}
}
catch {
print(error)
}
}
}.resume()
}
}
struct MagazineBannerView: View{
#ObservedObject var list = MagazineBannerVM()
public let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
#State var currentIndex = 0
#State var totalImages = 2
var body: some View{
ScrollView(.horizontal) {
GeometryReader { proxy in
TabView(selection: $currentIndex) {
HStack{
ForEach(list.datas, id: \.id){ item in
Group{
AsyncImage(url: URL(string: item.url)){ image in
image
.resizable()
.frame(width:UIScreen.main.bounds.width, height: 122)
}placeholder: {
Image("logo_gray").resizable()
.frame(width:UIScreen.main.bounds.width, height: 122)
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.onReceive(timer, perform: { _ in
withAnimation{
currentIndex = currentIndex < totalImages ? currentIndex + 1: 0
}
})
}
}
}
}
I want to change images after every 2 seconds and every images has
full width as the screen width
And it is showing the half of screen width and showing both images in
single view

Updating a Progress Value in SwithUI

I am trying to update a progress bar using the DispatchQueue.main.async (based on #Asperi's reply at but this does not work! Update progress value from fileImporter modifier in SwiftUI). The problem I am having is the progress updates waits until end of the task and then updates the progress bar which is not the expected behavior.
I put the file processing code within DispatchQueue.global().async, which executes the code right away and updates the Progress bar immediately (goes to 100% instantly) but the file processing task is still not complete. How do I get this to work the right way. Below is the code I am executing. Appreciate your help
Below is the ContentView:
struct ContentView: View {
#ObservedObject var progress = ProgressItem()
#State var importData: Bool = false
var body: some View {
VStack {
Button(action: {importData = true}, label: {Text("Import Data")})
.fileImporter(isPresented: $importData, allowedContentTypes: [UTType.plainText], allowsMultipleSelection: false) { result in
do {
guard let selectedFile: URL = try result.get().first else { return }
let secured = selectedFile.startAccessingSecurityScopedResource()
DispatchQueue.global().async { // added this to import file asynchronously
DataManager(progressBar: progress).importData(fileURL: selectedFile)
}
if secured {selectedFile.stopAccessingSecurityScopedResource()}
} catch {
print(error.localizedDescription)
}
}
ProgressBar(value: $progress.progress, message: $progress.message).frame(height: 20)
}
}
}
ProgressBar View:
struct ProgressBar: View {
#Binding var value: Double
#Binding var message: String
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle().frame(width: geometry.size.width , height: geometry.size.height)
.opacity(0.3)
.foregroundColor(Color(UIColor.systemTeal))
Rectangle().frame(width: min(CGFloat(self.value)*geometry.size.width, geometry.size.width), height: geometry.size.height)
.foregroundColor(Color(UIColor.systemBlue))
.animation(.linear)
//Text("\(self.value)")
}.cornerRadius(45.0)
}
}
}
DataManager that processes the file. Right now I have a thread.sleep(0..001) to simulate reading of the file
class DataManager {
#Published var progressBar: ProgressItem? = nil
init(progressBar: ProgressItem? = nil) {
self.progressBar = progressBar
}
func importData(fileURL: URL, delimeter: String = ",") {
let lines = try! String(contentsOf: fileURL, encoding: .utf8).components(separatedBy: .newlines).filter({!$0.isEmpty})
let numlines = lines.count
for index in 0..<numlines {
let line = String(lines[index])
Thread.sleep(forTimeInterval: 0.001)
print("\(line)")
DispatchQueue.main.async { // updating the progress value here
self.progressBar?.progress = Double(index)/Double(numlines) * 100
self.progressBar?.message = line
}
}
}
}
ProgressItem:
class ProgressItem: ObservableObject {
#Published var progress: Double = 0
#Published var message: String = ""
}

SwiftUI: Best approach for fetching data and passing to SubViews?

I have a parent view DetailView and I want to query for heart rates during a workout. After I get back the heart rates I want to pass them to a child View. The code below, the heart rates aren't getting passed to HeartRateRecoveryCard ?
import SwiftUI
import HealthKit
struct DetailView: View {
#State var heartRateSamples: [HKQuantitySample] = []
var body: some View {
HeartRateRecoveryCard(heartRateSamples: heartRateSamples)
.onAppear {
getHeartRatesFromWorkout()
}
}
private func getHeartRatesFromWorkout() {
guard let workout = SelectedWorkoutSingleton.sharedInstance.selectedWorkout else { return }
print("workout = \(workout.startDate)")
WorkoutManager.getHeartRateSamplesFrom(workout: workout) { (samples, error) in
guard let unwrappedSamples = samples else {
print("no HR samples")
return }
if let unwrappedError = error {
print("error attempting to get heart rates = \(unwrappedError)")
}
print("we have \(unwrappedSamples.count) heart rates")
DispatchQueue.main.async {
heartRateSamples = unwrappedSamples
}
}
}
}
struct HeartRateRecoveryCard: View {
//Don't download HR samples for this view the parent view should download HR samples and pass them into it's children views
#State var heartRateSamples: [HKQuantitySample]
#State var HRRAnchor = 0
#State var HRRTwoMinutesLater = 0
#State var heartRateRecoveryValue = 0
var body: some View {
VStack(alignment: .leading) {
Text("\(heartRateRecoveryValue) bpm")
Text("\(HRRAnchor) bpm - \(heartRateRecoveryValue) bpm")
SwiftUILineChart(chartLabelName: "Heart Rate Recovery", entries: convertHeartRatesToLineChartDataAndSetFormatter(heartRates: heartRateSamples).0, xAxisFormatter: convertHeartRatesToLineChartDataAndSetFormatter(heartRates: heartRateSamples).1)
.frame(width: 350, height: 180, alignment: .center)
.onAppear {
print("heart rate count = \(heartRateSamples.count)")
if let unwrappedHRRObject = HeartRateRecoveryManager.calculateHeartRateRecoveryForHRRGraph(heartRateSamples: heartRateSamples) {
HRRAnchor = unwrappedHRRObject.anchorHR
HRRTwoMinutesLater = unwrappedHRRObject.twoMinLaterHR
heartRateRecoveryValue = unwrappedHRRObject.hrr
}
}
}
}

How to Stop a user from passing the login page if their info is not correct IOS

In my IOS app, I'd like to send a message to a user who tries to login with bad credentials and notify them of this with a pop up. Currently, My database can recognize a login error but my swift code doesn't see the error condition until after it dismisses the login page and enters the app.
The flask/python code that accesses the database looks like this:
#app.route('/login', methods=['GET', 'POST'])
def login():
mydb = mysql.connector.connect(host="localhost", user="root", passwd="Pass", database = "events")
if request.method == 'POST':
mycursor = mydb.cursor()
username = request.form['username']
password = request.form['password']
mycursor.execute('SELECT* FROM accounts WHERE username = %s AND password = %s', (username, password,))
account = mycursor.fetchone()
if account:
try:
mydb.commit()
mydb.close()
except e:
# Rollback in case there is any error
print("Error: ", e)
mydb.rollback()
return make_response("Success!", 200)
else:
return make_response("username/password combination dne", 500)
The swiftui code that contacts the data base inside my app looks like this:
struct LogInView: View {
#State var username: String = ""
#State var password: String = ""
#State var email: String = "test#gmail.com"
#Binding var didLogin: Bool
#Binding var needsAccount: Bool
#State var errorString: String = ""
func send(_ sender: Any, completion: #escaping (String) -> Void) {
let request = NSMutableURLRequest(url: NSURL(string: "http://localhost/login")! as URL)
request.httpMethod = "POST"
self.username = "\(self.username)"
self.password = "\(self.password)"
self.email = "\(self.email)"
let postString = "username=\(self.username)&password=\(self.password)&c=\(self.email)"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
if error != nil {
print("error=\(String(describing: error))")
//put variable that triggers error try again view here
self.didLogin = false
self.errorString = String(describing: error)
completion(self.errorString)
return
}else{
self.didLogin = true
completion(String(describing: error))
}
print("response = \(String(describing: response))")
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("responseString = \(String(describing: responseString))")
if let httpResponse = response as? HTTPURLResponse {
self.errorString = String(httpResponse.statusCode)
}
}
task.resume()
}
var body: some View {
VStack{
Spacer()
WelcomeText()
UserImage()
TextField("Username", text: $username)
.padding()
.background(Color(.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("Password", text: $password)
.padding()
.background(Color(.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: {
self.send((Any).self){ array in
self.errorString = array
}/*
if self.errorString == "500"{
self.didLogin = false
}
else{
self.didLogin = true
}
}*/
},
label: {Text("LOGIN")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.orange)
.cornerRadius(15.0)})
.shadow(radius: 5)
.padding(.bottom, 10)
Button(action: {
self.needsAccount = true
}, label: {Text("Not a member yet? Sign up here")})
Spacer()
}.padding().background(Color.white).edgesIgnoringSafeArea(.all)
}
}
ContentView:
import SwiftUI
import Mapbox
import CoreLocation
struct ContentView: View {
#ObservedObject var annotationsVM: AnnotationsVM //= AnnotationsVM()
#ObservedObject var VModel: ViewModel //= ViewModel()
#ObservedObject var locationManager: LocationManager //= LocationManager()
#ObservedObject var data: DataFetcher
// #ObservedObject var mapViewCoordinator = MapViewCoordinator()
init() {
let vm = ViewModel()
VModel = vm
annotationsVM = AnnotationsVM(VModel: vm)
locationManager = LocationManager()
data = DataFetcher()
}
var userLatitude: CLLocationDegrees {
return (locationManager.lastLocation?.latitude ?? 0)
}
var userLongitude: CLLocationDegrees {
return (locationManager.lastLocation?.longitude ?? 0)
}
var lat: Double {
return (VModel.lat ?? 0)
}
var long: Double {
return (VModel.lon ?? 0)
}
var Userlat: Double {
return (VModel.userLatitude)
}
var Userlon: Double {
return (VModel.userLongitude)
}
//#State var searchedLocation: String = ""
#State private var annotationSelected: Bool = false
#State private var renderingMap: Bool = true
#State private var searchedText: String = ""
#State private var showResults: Bool = false
#State private var events: [eventdata] = []
#State private var showMoreDetails: Bool = false
#State private var didLogin: Bool = false
#State private var needsAccount: Bool = false
#State private var selectedAnnotation: MGLAnnotation? = nil
var body: some View {
VStack{
ZStack(alignment: Alignment(horizontal: .leading, vertical: .top)){
MapView(annotationSelected: $annotationSelected, renderingMap: $renderingMap, visited: $annotationsVM.visited, showMoreDetails: $showMoreDetails, selectedAnnotation: $selectedAnnotation, VModel: VModel, locationManager: locationManager, aVM: annotationsVM, data: data, annos: $annotationsVM.annos)
.edgesIgnoringSafeArea(.all)
if showResults == true && searchedText.count >= 1 {
Text("").frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.white).edgesIgnoringSafeArea(.all)
//this is pretty ghetto but whatever
}
VStack{
HStack(alignment: .top){
if showResults == false {
SettingsButton()
}
Spacer()
SearchBar(annotation: annotationsVM, VModel: VModel, searchedText: $searchedText, showResults: $showResults, showMoreDetails: $showMoreDetails)
// SearchBar(annotation: annotationsVM) { sender in
// self.searchedLocation = sender.searchText.text
// }
Spacer()
if showResults == false {
MessageButton()
}
}.padding()
//Update Annotation Button
// Button (action: {
// let delayInSeconds = 1.5
// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
// self.annotationsVM.addNextAnnotation(address: "22 Sunset Ave, East Quogue, NY")
//
// print("\(self.annotationsVM.annos)")
// print("User Coords: \(self.VModel.userLatitude), \(self.VModel.userLongitude)")
// }
// }, label: {Text("Press to update annotation")})
if showResults == true && searchedText.count >= 1 {
SearchResults(VModel: VModel, annotation: annotationsVM, showResults: $showResults, searchedText: $searchedText)
}
Spacer()
HStack(alignment: .bottom) {
if renderingMap {
Text("The Map is Rendering...")
}
}
//Side Note: If the Create Event Button is pressed, the currently selected annotation is unselected, so that'll need to be fixed
HStack(alignment: .bottom) {
if annotationSelected {
CreateEventButton(annotation: annotationsVM, annotationSelected: $annotationSelected)
}
}.padding()
}
VStack {
Spacer()
HStack {
Spacer()
if annotationsVM.annotationPlacementFailed == true {
AnnotationPlacementErrorView(annotationPlacementFailed: $annotationsVM.annotationPlacementFailed, annotation: annotationsVM, searchedText: $searchedText)
}
Spacer()
}
Spacer()
}
VStack {
Spacer()
HStack{
Spacer()
if self.showMoreDetails == true {
MoreDetailsView(searchedText: $searchedText, showMoreDetails: $showMoreDetails, selectedAnnotation: $selectedAnnotation)
//Instead of passing in searchedText, we need to pass in the mapView...idk how though
}
Spacer()
}
Spacer()
}
if self.didLogin == false {
LogInView(didLogin: $didLogin, needsAccount: $needsAccount)
}
if self.needsAccount == true {
SignUpView(didLogin: $didLogin, needsAccount: $needsAccount)
}
}
}
}
}
I'm not sure if this is a database//server issue or swiftui/httpresponse issue. Any insight is greatly appreciated
how does it dismiss the login page? assuming you use didLogin to check, you should default didLogin to false. so the user is not logged in until didLogin = true, meaning it will wait till you get a response from your http request. something like this:
#State private var didLogin: Bool = false
if didLogin {
ShowSomeViewAfterLogin()
} else {
ShowLogin()
}

SwiftUI #ObservedObject does not get updated on Async fetch of the image

I have an app that fetches a list of items with images URL's from remote API, and then it has to fetch an image per item from given url inside that item's position.
The problem is that when scrolling up and down and thus removing list items from view and moving them back into view they do show up. However on initial load they stay in "loading" status forever until moved out and in.
My code:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var artObjectStore: ArtObjectStore
#State private var pageCount = 1
#State private var tappedLink: String? = nil
#Environment(\.imageCache) var cache: ImageCache
var body: some View {
NavigationView {
Form {
Section(header: Text("Art")) {
List {
ForEach(artObjectStore.artObjects, id: \.self) { artObject in
self.link(for: artObject)
}
Button(action: loadMore) {
Text("")
}
.onAppear {
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 10)) {
self.loadMore()
}
}
}
}
}
.navigationBarTitle("Art objects")
}
.onAppear(perform: loadMore)
}
func loadMore() {
pageCount += 1
artObjectStore.loadMore(pageCount)
}
private func link(for artObject: ArtObject) -> some View {
let selection = Binding(get: { self.tappedLink },
set: {
UIApplication.shared.endEditing()
self.tappedLink = $0
})
return NavigationLink(destination: DetailView(artObject: artObject, cache: self.cache),
tag: artObject.id,
selection: selection) {
HStack(alignment: .center) {
VStack(alignment: .leading){
Text("\(artObject.title)").font(.system(size: 12))
Text("\(artObject.principalOrFirstMaker)").font(.system(size: 9)).foregroundColor(.gray)
}
Spacer()
AsyncImage(
url: URL(string: artObject.headerImage.url)!,
cache: self.cache,
width: 200,
height: 50
)
}
}
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Image container:
import SwiftUI
import Combine
import Foundation
class ImageLoader: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
private var cancellable: AnyCancellable?
#Published var image: UIImage? {
willSet {
objectWillChange.send()
}
}
private let url: URL
private var cache: ImageCache?
init(url: URL, cache: ImageCache? = nil) {
self.url = url
self.cache = cache
}
deinit {
cancellable?.cancel()
}
private func cache(_ image: UIImage?) {
image.map { cache?[url] = $0 }
}
func load() {
if let image = cache?[url] {
self.image = image
return
}
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.handleEvents(receiveOutput: { [weak self] in self?.cache($0) })
.receive(on: DispatchQueue.main)
.assign(to: \.image, on: self)
}
func cancel() {
cancellable?.cancel()
}
}
struct AsyncImage: View {
#ObservedObject private var loader: ImageLoader
private let width: CGFloat?
private let height: CGFloat?
#State var spin = false
init(url: URL, cache: ImageCache? = nil, width: CGFloat? = nil, height: CGFloat? = nil) {
loader = ImageLoader(url: url, cache: cache)
self.width = width
self.height = height
}
var body: some View {
image
.onAppear(perform: loader.load)
.onDisappear(perform: loader.cancel)
}
private var image: some View {
Group {
if loader.image != nil {
Image(uiImage: loader.image!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: width, height: height)
} else {
Image("loadingCircle")
.resizable()
.frame(width: 20, height: 20)
.rotationEffect(.degrees(spin ? 360 : 0))
.animation(Animation.linear(duration: 0.8).repeatForever(autoreverses: false))
.onAppear() {
self.spin.toggle()
}
}
}
}
}
protocol ImageCache {
subscript(_ url: URL) -> UIImage? { get set }
}
struct TemporaryImageCache: ImageCache {
private let cache = NSCache<NSURL, UIImage>()
subscript(_ key: URL) -> UIImage? {
get { cache.object(forKey: key as NSURL) }
set { newValue == nil ? cache.removeObject(forKey: key as NSURL) : cache.setObject(newValue!, forKey: key as NSURL) }
}
}
struct ImageCacheKey: EnvironmentKey {
static let defaultValue: ImageCache = TemporaryImageCache()
}
extension EnvironmentValues {
var imageCache: ImageCache {
get { self[ImageCacheKey.self] }
set { self[ImageCacheKey.self] = newValue }
}
}
I did try to add willSet on the image, that doesn't seem to work. Can you help me?
All below tested with Xcode 11.4 / iOS 13.4
Modified worked AsyncImage
I changed Group to VStack and it started updating
private var image: some View {
VStack { // << here !!
if loader.image != nil {
Modified worked image loader
class ImageLoader: ObservableObject {
#Published var image: UIImage?
private var cancellable: AnyCancellable?
private let url: URL
private var cache: ImageCache?
init(url: URL, cache: ImageCache? = nil) {
self.url = url
self.cache = cache
}
deinit {
cancellable?.cancel()
}
private func cache(_ image: UIImage?) {
self.image = image
image.map { cache?[url] = $0 }
}
func load() {
if let image = cache?[url] {
self.image = image
return
}
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.receive(on: DispatchQueue.main)
.replaceError(with: nil)
.sink(receiveValue: { [weak self] in self?.cache($0) })
}
func cancel() {
cancellable?.cancel()
}
}

Resources