Xcode 11 Swift 5 Updating a label in the non-main thread - swift5

Apologies if this has been answered but I've tried and failed miserably to find it.
I'm very new to "real" programming despite a long time working with "simple" languages such as VB, VBA.
I have a function that retrieves data from a URL and I want to update a UILabel when it's finished. I think I finally got my head around async calls and I thought I was doing the right thing calling the main thread to update the label but I'm still getting an error.
the error is UILabel.text must be used from main thread only
getBGs { error in
if let error = error {
print("\(error) error occured")
} else {
DispatchQueue.main.async {
let today = Date()
let formatter1 = DateFormatter()
formatter1.dateFormat = "E d-MMM-yyyy hh:mm:ss"
self.getBGsText.text = "Retrieved \(BGs?.count) at \(formatter1.string(from: today))"
}
}
}

Related

Core-data insert multiple objects

i am struggling again to solve a core Data task which keeps failing randomly on me
The following code is building my initial database, which is necessary for the app to work properly
(...)
let context = await (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
(...)
for person in groupOfPeople { //singlePerson (codable Struct) <> SinglePerson (NSManagedObject)
saveSinglePerson(sPerson: person, context: context)
counter+=1
}
logComment = logComment + "..success!(\(counter))"
func saveSinglePerson(sPerson: singlePerson, context: NSManagedObjectContext) {
let newSinglePerson = SinglePerson(context: context)
newSinglePerson.id = sPerson.ID
newSinglePerson.name = sPerson.name
newSinglePerson.age = sPerson.age
(...)
context.performAndWait {
do {
try context.save()
}catch let error {
print(error)
Logging.insertError(message: "IMPORT ERROR: \(error.localizedDescription)", location: "buildDatabase20")
}
}
}
Now here's my problem:
At first i didn't even notice there is a problem, because everything is working fine and all objects get saved as they are supposed to be,
but indeed there is one, because: i am randomly getting an error like so:
error= Error Domain=NSCocoaErrorDomain Code=134030 "An error occurred while saving." UserInfo={NSAffectedObjectsErrorKey=(
"<AppName.SinglePerson: 0x60000104e7b0> (entity: SinglePerson; id: 0x600003337ce0 <x-coredata:///SinglePerson/t3081F988-C5D1-4532-AD81-46F3B4B10215139>; data: {\n id = 138;\n name = testname;\n age = \"25\";\n })"
and i get this error multiple times (20x-150x), with just this one single ID, in this example 138, but it is a different id each time...
i investigate this situation for days now, and i just can't wrap my head around this..
what i found out by now is:
the method should insert 150 rows, and if this error occurs it is not just a count of 149, it's like 87, or 127, or whatever
seems like an object gets stuck in the context, and every execution after the first error fails and is throwing the (same) error..
i tried to fetch those new written data directly after i inserted them, and i always get the same (wrong) count of 150..
i know that this count is not legit because if i take a look at the sqllite file, is see just 87, or 127 or whatever row count..
i do this fetch again with the same context, this is why i think that the issue is within my NSManaged context..
why is this happening on me? and why does this happen sometimes but not all the time?
How do i solve it?
i've found a solution to fix this issue, even though i now know that i will have rework all Core Data interactions from the ground up, to make it real stable and reliable..
this is my first swift project, so along the way things got pretty messy tbh :)
fix: the fact that i save all created objects at once now instead of saving each item on its own, did work and solved the issue for me at this very moment :)
maybe this is helpful for somebody else too ;)
(...)
let context = await (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
(...)
var personArray = [SinglePerson]()
for person in groupOfPeople {
let newSinglePerson = SinglePerson(context: context)
newSinglePerson.id = sPerson.ID
newSinglePerson.name = sPerson.name
newSinglePerson.age = sPerson.age
(...)
personArray.append(newSinglePerson)
}
context.performAndWait {
do {
try context.save()
} catch let error {
print(error.localizedDescription)
}
}

How to fix thread or handle error in Swift?

I can't get rid of an error in the program using Core ML image recognize!
My code is basically debugged and works with a large number of files, but on 421 files the EXC_BAD_ACCESS error occurs (and I changed the order of processing files to eliminate the problem of broken files)
I have already translated all potentially dangerous objects into singletons to avoid leaks, debugged and tidied the sqlite database, rewritten the code using the recommended generateCGImagesAsynchronously operator to get a picture from a video file, everything is cleaned up and works without leaks, but the error still occurs
Directly the code for extracting text objects is taken from the network and slightly upgraded, maybe there is a problem with it?, help!
CreateAltModel3.shared.handler = VNImageRequestHandler(ciImage: inputImage!)
CreateAltModel3.shared.request = VNCoreMLRequest(model: CreateAltModel3.shared.visionModel!) { [weak self] req3, error in
output_handler(req3.results) }
do {try CreateAltModel3.shared.handler!.perform([CreateAltModel3.shared.request!])} catch {print ("hadler3 error - request3")}
here is the code for the output_handler function
func output_handler(_ results: [Any]?) {
guard let results = results as? [VNClassificationObservation]
else {return}
for class1 in results {
if (class1.confidence > 0.05) { // Only show greater than 5%
let fileID = filesArray[currentFileIndex]?.index
if percentRecogn <= Int(round(class1.confidence*100)) {
findKeyStr = (class1.identifier + " " + String(round(class1.confidence*100)) + "%")
resultKeyStr.0 = Int64(fr)
let comma = (resultKeyStr.1 == "") ? "" : ", "
let cutDubbing = (resultKeyStr.1 == class1.identifier) ? resultKeyStr.1 : resultKeyStr.1 + comma + class1.identifier
resultKeyStr.1 = cutDubbing
resultKeyStr.2 = Int64(fileID!)
}
}
}
}
but the error as I understand it occurs after exiting this function
on the next line of code
more code to spread does not make sense because I will only confuse you with extra information, I will only add that this code works in a loop in the global stream and outputs information to the label and ImageView in the main stream as expected, and everything seems to be debugged but I can not understand the nature of the error, perhaps something related to streams but there is not even an idea where to dig)
You cannot (or at least should not) use a single VNCoreMLRequest from multiple threads at a time.

Swift 3 Completion Handler on Google Places Lookup. Due to delay how do I know when Im "done"?

Sorry, newbie here and Ive read extensively about completion handlers, dispatch queues and groups but I just can't get my head around this.
My app loads an array of Google Place IDs and then wants to query Google to get full details on each place. The problem is, due to async processing the Google Lookup Place returns immediately and the callback happens much further down the line so whats the "proper way" to know when the last bit of data has come in for my inquiries because the function ends almost immedately ?
Code is attached. Thanks in advance.
func testFunc() {
let googlePlaceIDs = ["ChIJ5fTXDP8MK4cRjIKzek6L6NM", "ChIJ9Wd6mGYGK4cRiWd0_bkohHg", "ChIJaeXT08ASK4cRkCGpGgzYpu8", "ChIJkRkS4BapK4cRXCT8-SJxNDI", "ChIJ3wDV_2zX5IkRtd0hg2i1LhE", "ChIJb4wUsI5w44kRnERe7ywQaJA"]
let placesClient = GMSPlacesClient()
for placeID in googlePlaceIDs {
placesClient.lookUpPlaceID(placeID, callback: { (place, error) in
if let error = error {
print("lookup place id query error: \(error.localizedDescription)")
return
}
guard let place = place else {
print("No place details for \(placeID)")
return
}
print("Place Name = \(place.name)")
})
}
print("Done")
}

Do - Catch error handling Swift 2.0

I have read through numerous posts regarding conversion between older versions of swift and swift 2.0 on the issue of Do-catch error handling. However each and every one of them seem different to my personal issue.
Besides solving my personal issue I'm fairly curious as to what the general idea is behind this concept, because I simply can not figure out how this works on a low level scale just by reading all these topics.
I'll post my personal issue below, but I'd also very much appreciate some sort of general explanation about how this do-catch method works.
if(urlResponse.statusCode == 200) {
self.tweets = NSJSONSerialization.JSONObjectWithData(responseData,
options: NSJSONReadingOptions.MutableContainers,
error: &jsonParseError) as? NSMutableArray
}
the error shows at the line:
error: &jsonParseError) as? NSMutableArray
Change your code to
if(urlResponse.statusCode == 200) {
do {
self.tweets = try NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.MutableContainers) as? NSMutableArray
} catch let jsonParseError {
print("Parse error = \(jsonParseError)")
}
}
You can find more about error handling here.

Can you execute an Applescript script from a Swift Application

I have a simple AppleScript that sends an email. How can I call it from within a Swift application?
(I wasn't able to find the answer via Google.)
As Kamaros suggests, you can call NSApplescript directly without having to launch a separate process via NSTask (as CRGreen suggests.)
Swift Code
let myAppleScript = "..."
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: myAppleScript) {
if let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(
&error) {
print(output.stringValue)
} else if (error != nil) {
print("error: \(error)")
}
}
Tested: one can do something like this (arbitrary script path added):
import Foundation
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["~/Desktop/testscript.scpt"]
task.launch()
For anyone who is getting the warning below for Swift 4, for the line while creating an NSAppleEventDescriptor from zekel's answer
Non-optional expression of type 'NSAppleEventDescriptor' used in a check for optionals
You can get rid of it with this edited short version:
let myAppleScript = "..."
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: myAppleScript) {
if let outputString = scriptObject.executeAndReturnError(&error).stringValue {
print(outputString)
} else if (error != nil) {
print("error: ", error!)
}
}
However, you may have also realized; with this method, system logs this message to console everytime you run the script:
AppleEvents: received mach msg which wasn't complex type as expected
in getMemoryReference.
Apparently it is a declared bug by an Apple staff developer, and is said to be 'just' a harmless log spam and is scheduled to be removed on future OS updates, as you can see in this very long apple developer forum post and SO question below:
AppleEvents: received mach msg which wasn't complex type as expected in getMemoryReference
Thanks Apple, for those bazillions of junk console logs thrown around.
You can try NSAppleScript, from Apple's Technical Note TN2084
Using AppleScript Scripts in Cocoa Applications
https://developer.apple.com/library/mac/technotes/tn2084/_index.html
NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:
#"\
set app_path to path to me\n\
tell application \"System Events\"\n\
if \"AddLoginItem\" is not in (name of every login item) then\n\
make login item at end with properties {hidden:false, path:app_path}\n\
end if\n\
end tell"];
returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
I struggled few hours, but nothing worked. Finally I managed to run AppleScript through shell:
let proc = Process()
proc.launchPath = "/usr/bin/env"
proc.arguments = ["/usr/bin/osascript", "scriptPath"]
proc.launch()
Dunno is this the best way to do it, but at least it works.
As of March 2018, I think the strongest answer on this thread is still the accepted answer from 2011. The implementations that involved using NSAppleScript or OSAScript suffered the drawbacks having some minor, but highly unpleasant, memory leaks without really providing any additional benefits. Anyone struggling with getting that answer to execute properly (in Swift 4) may want to try this:
let manager = FileManager()
// Note that this assumes your .scpt file is located somewhere in the Documents directory
let script: URL? = try? manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
if let scriptPath = script?.appendingPathComponent("/path/to/scriptName").appendingPathExtension("scpt").path {
let process = Process()
if process.isRunning == false {
let pipe = Pipe()
process.launchPath = "/usr/bin/osascript"
process.arguments = [scriptPath]
process.standardError = pipe
process.launch()
}
}
Update
The simple, accepted answer from 2011 has gotten more complex. Apple has deprecated the launch() function as of as of 10.14, suggesting that we "use run() instead". Unfortunately, it's not a direct replacement. Fortunately, most of the original answer still holds, with a simple change.
Instead of the original line:
task.launch()
when you use run() you have to use it with try to catch possible errors. The line becomes:
try task.run()
The accepted answer above then becomes:
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["~/Desktop/testscript.scpt"]
try task.run()
However, this only works if the code is at the top level, not if it is inside a function. run() throws errors while launch() does not. If you use try inside a function, the function also has to be declared to throw errors. Or you can handle possible errors from run() inside the function.
This page from "Hacking With Swift" has examples of how to catch STDOUT and STDERR from the process.
I hope this helps. I'm very new at Swift and this is what worked for me.