iOS swift while loop freeze my app - ios

I'm developing an app where a user choose his accommodation and pick a time from date picker so the user can know what time the buss will arrive to his accommodation , on the simulator and iPad I'm having the appropriate results however I'm facing this issue on iPhone real device, this is the code :
mTimeString = "5:07 AM"
it will be searched in the array if not found , minus seconds until it matches results in the array . but while loop is freezing my app , i tried to surround it by :
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT ,0)
dispatch_async(queue){}
its not freezing anymore but the I'm not getting the results according to the array
if(defaults.stringForKey(accommodationChoiceKey)?.toInt() == 40) {
var array = ["12:05 AM","12:25 AM","12:45 AM","1:05 AM","1:25 AM","1:45 AM","2:05 AM","2:25 AM","2:45 AM","3:05 AM","3:25 AM","3:45 AM","4:05 AM","4:25 AM","4:45 AM","5:05 AM","5:25 AM","5:45 AM","6:05 AM","6:25 AM","6:45 AM","7:05 AM","7:25 AM","7:45 AM","8:05 AM","8:25 AM","8:45 AM","9:05 AM","9:25 AM","9:45 AM","10:05 AM","10:25 AM","10:45 AM","11:05 AM","11:25 AM","11:45 AM","12:05 PM","12:25 PM","12:45 PM","1:05 PM","1:25 PM","1:45 PM","2:05 PM","2:25 PM","2:45 PM","3:05 PM","3:25 PM","3:45 PM","4:05 PM","4:25 PM","4:45 PM","5:05 PM","5:25 PM","5:45 PM","6:05 PM","6:25 PM","6:45 PM","7:05 PM","7:25 PM","7:45 PM","8:05 PM","8:25 PM","8:45 PM","9:05 PM","9:25 PM","9:45 PM","10:05 PM","10:25 PM","10:45 PM","11:05 PM","11:25 PM","11:45 PM"]
while (find(array, mTimeString) == nil) {
choiceToSeconds--
var newmTime = x.dateByAddingTimeInterval(Double(choiceToSeconds))
var mTimeString = dateFormatter.stringFromDate(newmTime)
if (find(array, mTimeString) != nil) {
transportationLabel.text = mTimeString
break
}
getReady(newmTime)
}
}

You are calculating on Background thread which is okay , but you should NEVER update UI element from background thread ( transportationLabel.text = mTimeString ) , this should be done from main Thread , for example
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
if(defaults.stringForKey(accommodationChoiceKey)?.toInt() == 40) {
var array = ["12:05 AM","12:25 AM","12:45 AM","1:05 AM","1:25 AM","1:45 AM","2:05 AM","2:25 AM","2:45 AM","3:05 AM","3:25 AM","3:45 AM","4:05 AM","4:25 AM","4:45 AM","5:05 AM","5:25 AM","5:45 AM","6:05 AM","6:25 AM","6:45 AM","7:05 AM","7:25 AM","7:45 AM","8:05 AM","8:25 AM","8:45 AM","9:05 AM","9:25 AM","9:45 AM","10:05 AM","10:25 AM","10:45 AM","11:05 AM","11:25 AM","11:45 AM","12:05 PM","12:25 PM","12:45 PM","1:05 PM","1:25 PM","1:45 PM","2:05 PM","2:25 PM","2:45 PM","3:05 PM","3:25 PM","3:45 PM","4:05 PM","4:25 PM","4:45 PM","5:05 PM","5:25 PM","5:45 PM","6:05 PM","6:25 PM","6:45 PM","7:05 PM","7:25 PM","7:45 PM","8:05 PM","8:25 PM","8:45 PM","9:05 PM","9:25 PM","9:45 PM","10:05 PM","10:25 PM","10:45 PM","11:05 PM","11:25 PM","11:45 PM"]
while (find(array, mTimeString) == nil) {
choiceToSeconds--
var newmTime = x.dateByAddingTimeInterval(Double(choiceToSeconds))
var mTimeString = dateFormatter.stringFromDate(newmTime)
if (find(array, mTimeString) != nil) {
// This should be done on Main Thread
dispatch_async(dispatch_get_main_queue()) {
transportationLabel.text = mTimeString
}
break
}
getReady(newmTime)
}
}
}

Try replacing
transportationLabel.text = mTimeString
by
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
transportationLabel.text = mTimeString
}];
UI operations must be executed from main thread
EDIT:
In Swift:
NSOperationQueue.mainQueue().addOperationWithBlock({
transportationLabel.text = mTimeString
})

Related

Problems with list manipulation in Swift after iOS update 14.4.2

For some years, I use this simple code to read and manipulate a list in Swift:
if (Helper.hasSubscription()) {
self.allVisitedTrackHandles = Database.getAllCompletedTrackHandles(withDeleted: false, userid: self.userid)
if (self.allVisitedTrackHandles.count > 0) {
var counter = 0
for th: TrackHandle in self.allTrackHandles {
if self.allVisitedTrackHandles.contains(where: { $0.trackid == th.trackid }) {
for thv: TrackHandle in self.allVisitedTrackHandles {
if thv.trackid == th.trackid {
self.allTrackHandles[counter].date_in_milliseconds = thv.date_in_milliseconds
break
}
}
self.allTrackHandles[counter].visited = 1
}
counter += 1
}
}
}
After updating my iOS device to iOS 14.4.2, app is crashing on this line:
for th: TrackHandle in self.allTrackHandles
Only feedback Xcode gives me is this: Thread 79: EXC_BAD_ACCESS (code=1, address=0x12f100010)
I don't know why that line would suddenly start crashing; There is nothing in the code shown that would cause the crash. Are you updating the array somewhere else? Is this, perhaps, a threading issue?
You can refactor this code to make it simpler, clearer and more efficient. This may help;
if Helper.hasSubscription() {
self.allVisitedTrackHandles = Database.getAllCompletedTrackHandles(withDeleted: false, userid: self.userid)
if !self.allVisitedTrackHandles.isEmpty {
for index in 0..<self.allTrackHandles.count {
if let visited = self.allVisitedTrackHandles.first { $0.track id == self.allTrackHandles[index].track id } {
self.allTrackHandles[index].date_in_milliseconds = visited.date_in_milliseconds
}
self.allTrackHandles[index].visited = 1
}
}
}

SocketScan Getting the Battery Level in Swift

Whatever I seem to try I cannot currently get back the Battery level from the iOS/SocketScan API. I am using version 10.3.36, here is my code so far:
func onDeviceArrival(result: SKTRESULT, device deviceInfo: DeviceInfo!) {
print("onDeviceArrival:\(deviceInfo.getName())")
scanApiHelper.postGetBattery(deviceInfo, target: self, response: #selector(onGetBatteryInfo))
}
func onGetBatteryInfo(scanObj: ISktScanObject) {
let result:SKTRESULT = scanObj.Msg().Result()
print("GetBatteryInfo status:\(result)")
if (result == ESKT_NOERROR) {
let batterylevel = scanObj.Property().getUlong()
print("Battery is:\(batterylevel)")
} else {
print("Error GetBatteryInfo status:\(result)")
}
However, the values I get back are:
GetBatteryInfo status:0
Battery is:1677741312
If my code is correct then how do I make the Battery result I get back a meaningful result, like a percentage? If I'm way off then how do I get back info like the battery level, firmware version etc?
Thanks
David
EDIT: SKTBATTERY_GETCURLEVEL isn't supported in Swift. However, the docs explain that the battery level response includes the min, current and max levels encoded in the first, second and third bytes, respectively.
The following is equivalent to using SKTBATTERY_GETCURLEVEL
Swift
func onGetBatteryInfo(scanObj: ISktScanObject) {
let result:SKTRESULT = scanObj.Msg().Result()
if(SKTSUCCESS(result)){
let batteryInfo = scanObj.Property().getUlong();
let batteryMin = ((batteryInfo >> 4) & 0xff);
let batteryCurrent = ((batteryInfo >> 8) & 0xff);
let batteryMax = ((batteryInfo >> 12) & 0xff);
let batteryPercentage = batteryCurrent / (batteryMax - batteryMin);
print("Battery is:\(batteryPercentage)")
self.setBatteryLevel = batteryPercentage
self.tableView.reloadData
} else {
print("Error GetBatteryInfo status:\(result)")
}
}
Objective-C
-(void) onGetBatteryInfo:(ISktScanObject*)scanObj {
SKTRESULT result=[[scanObj Msg]Result];
if(SKTSUCCESS(result)){
long batteryLevel = SKTBATTERY_GETCURLEVEL([[scanObj Property] getUlong]);
NSLog(#"BatteryInfo %ld", batteryLevel);
[self setBatteryLevel:batteryLevel];
[self.tableView reloadData];
} else {
NSLog(#"Error GetBatteryInfo status: %ld",result);
}
}
Here's code I use. Theres a variable defined in appDelegate for the batteryPercentage, and that is read when the v value is needed. The value is updated each 120 seconds by a timer, this way actions can occur as the level drops etc.
func onBatteryLevel (scanObj: ISktScanObject) {
let result: SKTRESULT = scanObj.Msg().Result()
if (SKTRESULT(result) > -1) {
let property: ISktScanProperty = scanObj.Property()
var batteryLevel = property.getUlong()
#if arch(x86_64) || arch(arm64)
batteryLevel = (batteryLevel<<(48))>>(56)
#else
batteryLevel = (batteryLevel<<(48-32))>>(56-32)
#endif
batteryPercentage = Int(batteryLevel)
} else {
debug ("data error \(result)")
}
}
For Swift 4 I just came across this problem and came up with the following solution.
var lastDeviceConnected : CaptureHelperDevice? {
didSet {
guard let lastDevice = self.lastDeviceConnected else { return }
lastDevice.getBatteryLevelWithCompletionHandler { result, batteryLevel in
guard result == SKTResult.E_NOERROR, let batteryLevel = batteryLevel else { return }
let minimum = SKTHelper.getMinimumLevel(fromBatteryLevel: Int(batteryLevel))
let maximum = SKTHelper.getMaximumLevel(fromBatteryLevel: Int(batteryLevel))
let current = SKTHelper.getCurrentLevel(fromBatteryLevel: Int(batteryLevel))
print("minimum: \(minimum)")
print("maximum: \(maximum)")
print("current: \(current)")
// current is out battery level in %
// minimum and maximum could for example be used for
// for a slider or something that visually presents
// the battery status
}
}
}
In my example I'm not handling the case that there could be no device or that the battery status might not have been retrieved as expected. I simply guard / return. In your example you might want to handle the issue.

Can't perform action in background and update progressView in mainThread

I've this method :
func stepThree() {
operation = "prDatas"
let entries = self.data.componentsSeparatedByString("|***|")
total = entries.count
for entry in entries {
++current
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
self.registerDB(entry)
})
}
status.setProgress(Float(current/total), animated: true)
finishAll()
}
I want to perform registerDB function and update my progressBar when complete.
I tested several way but never succeed
EDIT 1
Implementing #Russell proposition, work perfectly, but calculating value inside dispatch_async block always result to 0
Is there an issue about operations and multithread ?
method :
func stepThree() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
var current = 0
var total = 0
self.operation = "prDatas"
let entries = self.data.componentsSeparatedByString("|***|")
total = entries.count
for entry in entries {
++current
self.registerDB(entry)
dispatch_async(dispatch_get_main_queue(), {
print("value of 'current' is :" + String(current))
print("value of 'total' is :" + String(total))
print("Result is : " + String(Float(current/total)))
self.updateV(Float(current/total))
})
}
})
}
Console output :
value of 'current' is :71
value of 'total' is :1328
Result is : 0.0
Your code will update the status bar immediately - so the job will not have finished.
You need to move the update so that it actually follows the registerDB function, and then you have to make the call on the main thread. Here's an example - using dummy functions instead of your function calls, so that I can ensure it works as expected
func stepThree()
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let total = 5 // test data for demo
for entry in 0...total
{
// dummy function - just pause
sleep(1)
//self.registerDB(entry)
// make UI update on main queue
dispatch_async(dispatch_get_main_queue(),
{
self.setProgress(Float(entry)/Float(total))
})
}
})
}
func setProgress(progress : Float)
{
progressView.progress = progress
lblProgress.text = String(format: "%0.2f", progress)
}

UIActivityView Swift not functioning correcting?

#IBAction func getData(sender: AnyObject) {
activitySpinner.startAnimating();
activitySpinner.hidden = false;
edisonArray[0].EdisonLocation = edison1Location.text!;
edisonArray[1].EdisonLocation = edison2Location.text!;
edisonArray[2].EdisonLocation = edison3Location.text!;
edisonArray[3].EdisonLocation = edison4Location.text!;
edisonArray[4].EdisonLocation = edison5Location.text!;
edisonArray[0].EdisonComment = edison1Comment.text!;
edisonArray[1].EdisonComment = edison2Comment.text!;
edisonArray[2].EdisonComment = edison3Comment.text!;
edisonArray[3].EdisonComment = edison4Comment.text!;
edisonArray[4].EdisonComment = edison5Comment.text!;
for(var i = 0; i < edisonArray.count ; i++){
var edison = edisonArray[i];
edison = startDataCollection(edison);
edisonArray[i] = edison;
}
edison1Data.text = "True"
edison2Data.text = "True"
edison3Data.text = "True"
edison4Data.text = "True"
edison5Data.text = "True"
if(!edisonArray[0].EdisonBool) { edison1Data.text = "False" }
if(!edisonArray[1].EdisonBool) { edison1Data.text = "False" }
if(!edisonArray[2].EdisonBool) { edison1Data.text = "False" }
if(!edisonArray[3].EdisonBool) { edison1Data.text = "False" }
if(!edisonArray[4].EdisonBool) { edison1Data.text = "False" }
activitySpinner.stopAnimating();
activitySpinner.hidden = true;
}
The code above is for a project I'm working on. I'm trying to get the activity spinner working every time I run this code the spinner is not animating. Can someone please help me with this.
activitySpinner is an object of UIActivityIndicatorView
You are running your work in the main thread. Which will stop the animation from working correctly. Additionally, you are calling start and stop animation on the same frame of the runloop. Which can also cause it to not animate.
Finally, you can not update the user interface of the background thread. So if you are working with UIKit controls you will need to do so on the main thread.
Matt points out the correct method of invoking again on the main thread. But I suspect the full workflow would look like this
activitySpinner.startAnimating();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);())
{
//do any slow operations here in the background queue
doProcess();
//move back to the ui thread
dispatch_async(dispatch_get_main_queue())
{
//update any user interface info based on the above processing
updateUi();
activitySpinner.stopAnimating();
}
}
Like this:
#IBAction func getData(sender: AnyObject) {
activitySpinner.startAnimating();
dispatch_async(dispatch_get_main_queue()) { // <--
activitySpinner.hidden = false;
edisonArray[0].EdisonLocation = edison1Location.text!;
// ...
activitySpinner.stopAnimating();
activitySpinner.hidden = true;
}
}
But note that this will not make your code correct. If startDataCollection is time-consuming, you should not be doing it on the main thread. However, that's another question.

alternative to QOS_CLASS_UTILITY on ios7 using swift

I'm using QOS_CLASS_UTILITY in dispatch_async to perform some async operations.Sample code is below :
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)) {
var ip = "163.289.2." + "\(i)"
let foundThisOne = Ping.getIPAddress(ip)
dispatch_async(serialQueue) {
if !resultFound {
resultFound = foundThisOne
numProcessed++
if resultFound {
completion(existingIP:ip)
} else if numProcessed == 256 {
completion(existingIP: nil)
}
}
}
}
}
But "QOS_CLASS_UTILITY" is available on iOS8 onwards and now i need to support the same logic on iOS7 also.So what is better alternative to
QOS_CLASS_UTILITY which works on iOS7 using swift.
According to documentation (see Global Concurrent Queues) I'd say you can replace it with:
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)

Resources