Updating the main View from a background thread - Swift - swift

When I press a button I start a loop of calculations and need to be able to update the main view as it progresses. As this is a background thread the screen doesn't update until the call has completed. How do I achieve this in Swift? This is my current, simplified, approach which doesn't work.
#IBAction func calculatePower(sender: AnyObject) {
for day in 1...365 {
dispatch_async(dispatch_get_main_queue()) {
self.dayLabel.text = "\(day)"
}
}
}
I typically add some 0s onto the loop otherwise it completes too quickly going from just 1 to 365.
In Cocoa I would use a statement like:
[self performSelectorOnMainThread:#selector(updateDisplayEnergySunEquator:) withObject:[NSNumber numberWithDouble:distanceFromSun] waitUntilDone:YES];
Any ideas?

#IBAction functions are called in the main thread when the button is pressed. What happens is that your closures are queued on the main thread, but your main thread is busy working on your loop. Basically, you are doing asynchronous work within one thread.
To run the loop on another thread, wrap it in a dispatch_async and use another queue.
#IBAction func calculatePower(sender: AnyObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
for day in 1...365 {
dispatch_async(dispatch_get_main_queue()) {
self.dayLabel.text = "\(day)"
}
}
}
}

Related

Timer on table view Cell

there are 5 cells in table view, each have countdown on it, but countdown running fast, it decreases more than 1 second in one call.
this is my table view cell class, I have created a labelSatus here
class ActivityCell: UITableViewCell {
#IBOutlet weak var labelStatus: UILabel!
//Variable Declaration
var timer: Timer?
var totalTime:Double = 0
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func designCell(data:ActivityModal, removeTimer:Bool){
if removeTimer {
if let timer = self.timer {
timer.invalidate()
self.timer = nil
}
}
else{
let timeStampLimit = (data.date)/1000 + 86400 //for 24 hours
let currentTimeStamp = NSDate().timeIntervalSince1970
if timeStampLimit > currentTimeStamp{
self.totalTime = timeStampLimit - currentTimeStamp
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
}
}
#objc func updateTimer() {
self.labelStatus.text = String.getString(CommonUtils.convertTimeStampToHour(unixtimeInterval: self.totalTime, dateFormat:"HH:mm:ss"))
if self.totalTime != 0 {
self.totalTime -= 1 // decrease counter timer
} else {
if let timer = self.timer {
timer.invalidate()
self.timer = nil
}
}
}
}
There are a few issues to take care of when working with timers on table view cells. Note that the UITableView reuses the cell objects, so after a cell disappears from the screen after scrolling, it will be reused to be displayed in another place inside the table view.
Supposing you call designCell() in tableView:(_:cellForRowAt:) method, you might be creating more timers for a single cell (i.e. a lot of timers which will trigger the same cell's updateTimer() method). Note that a Timer will not be deallocated after you drop all your references to it, if the timer is still valid. At this point, you might be creating a new timer for a specific cell without having the chance to invalidate the old one.
This would be handled by stopping the timer in preapreForReuse() method from the table view cell subclass. This method is called when the cell is going to be reused:
class ActivityCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
self.timer?.invalidate()
self.timer = nil
}
}
This will solve (one of) the timer problem(s), but there is one more issue: you are going to lose the time tracked by that cell. There is no way to preserve that state into the cell itself. You must do it in the view controller and pass the displayed cell the time it needs to track in tableView(_:cellForRowAt:) method. If you are already doing it, then this is not a problem.
Another problem is that the timers will live forever (or until it's cell stops it). For some of the timers, if the cell object lost the reference to it, nobody will ever stop that timer anymore, so it will just fire every second until the app is killed.
I'm not really sure at this point whether the timer will retain the cell object using the target-selector method for creating a timer, but there is an issue regardless of that:
If the timer retains the cell object, that means that you will have memory leaks: that means that the timers and the cells will never be deallocated and will continuously use memory and processing resources;
If the timer does not retain the cell object, then there might occur a crash once the cell is being deallocated.
You should stop the cell's timer in tableView(_:didEndDisplaying:forRowAt) method. At this point, the cell is being hidden, so the timer makes no sense anymore.
Of course, all of this will lead you to another issue: preserving the time state for the cells. This handling should be done from the view controller presenting the table view, and not from the table view cell.
TL;DR:
You will have a lot of headaches having a timer on each cell, in fact it's almost impossible to handle things that way. You should have your timers corresponding to each cell in the view controller that presents the table view and the things will simplify a lot.

View/Window won't update until #IBAction is complete

I have a button linked to an IBAction that takes around 5-10 seconds to complete.
While the IBAction function is running I want to print progress messages in my view/window.
The only message that prints is the last one, and it prints after the IBAction has completed (ie the button is not blue).
When I step through the function in the debugger, no messages are visible until I'm out of the function (ie button is not blue).
Any idea on how to update the window while the callback is being executed?
embed the IBAction logic inside a background queue:
DispatchQueue.global(qos: .background).async {
// background code
}
and anytime you need to print the progress in your view/window embed that part inside:
DispatchQueue.main.async {
// user interface code
}
generally you may want to try this:
DispatchQueue.global(qos: .background).async {
//background code
DispatchQueue.main.async {
//your main thread
}
}

NSTimer with drawing barChart in Background

I'm using an NSTimer in an iOS App in background, which is saving some data every 30 seconds in an array. The app shows the last 10 values (values of 5 minutes) in a linechart.
My problem is to use the function of saving data into the array every 30 seconds also in background, when the app isn't on screen. I've written a lot of themes about this, but I don't understand it.
My timer is the following:
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: (#selector(ViewController.counting)), userInfo: nil, repeats: true)
func counting() {
timerCounter += 1 //Int
if timerCounter%30==0 {
arrayOfValues.append(...) //Appending the array
reloadLineChart() // reload chart
}
}
Could anyone show me how to solve this? I know, there must be something with the background-methods in the ViewController, but don't now what to type in.
I think there must be a function, which is counting in background and a function that is reloading the chart when I'm back in the app.
I might understand that you don't want to declare the counter in app delegate, for whatever reason you might have,although I would recommend it. However you can call the functions I mentioned, from the same class in which you have defined the counter. You would need to call it like this:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "applicationWillResignActive:",
name: UIApplicationWillResignActiveNotification,
object: nil)
}
func applicationWillResignActive(notification: NSNotification) {
// stop your counter
}
Then do the same with the other function. Hope is clear.
In your app delegate you can use this method :
func applicationWillResignActive(application: UIApplication) {}
The code you add in this function will run right before your app goes in the background . Therefore you can write there the code to stop the counter.
Afterwards you need to use the following function to activate the counter again:
func applicationWillEnterForeground(application: UIApplication) {}
The code you write in this function will run when you come back to the app again. Hope it is clear enough.

How to update UI at main queue in Swift?

I want to create a splash screen application that I also have a Class named EXClass.
for some reasons the EXClass must initialize in main thread and update some UI.
please reference following swift code.
func applicationDidFinishLaunching(aNotification: NSNotification) {
self.windowController = ......
self.windowController.showWindow(self)
// In order to show window at the very first time, I create a main queue
dispatch_async(dispatch_get_main_queue()) {
self.ex = EXClass() // this class must init in main thread
}
}
If I init EXClass in dispatch_async(dispatch_get_main_queue()) that init class is ok but update UI is no effect. why?

Swift - performSegueWithIdentifier in thread

i've got this type of problem :
With my app i'm able to connect my iPhone with a BLE device so when i connect their together i'm transported into an another view.
Into this view i've to check always if i'm connected yet in a thread func.
If my connection is lost i've to call the performSegueWithIdentifier method.
But... When i do it, xCode give me back a bad report that says :
This application is modifying the autolayout engine from a background
thread, which can lead to engine corruption and weird crashes. This
will cause an exception in a future release.
This is my short code :
override func viewDidLoad(){
super.ViewDidLoad()
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
while(true){
if(serial.connectedPeripheral == nil){
self.GoBackToBLE()
}
}
}
}
I want to precise that i can't use the method dispatch_get_main_queue because if i use that i can't do anything else into the view for example if i have a button I want to be able to press it and if i use dispatch_get_main_queue method i can't.
any suggestions? thanks a lot
You shouldn't do anything related to UI in dispatch_get_global_queue
you can split the things to run into 2 parts, 1 is related to UI other one is doing network calls/calculations etc.
override func viewDidLoad(){
super.ViewDidLoad()
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// do the ui stuff here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
// do everything which is not ui
})
})
}