I am trying to add activity indicator while updating annotation on the map. But it seems not to work such a way. Application screen disables and becomes frozen once process of update is started, so that's why probably activity indicator is not visible.
My question is: Is it possible to update annotations on the map asynchronous with the app, so activity indicator would be visible.
I believe it is really annoying for the user not to see indicator if it takes more then 2 sec.
Solution:
self.activityIndicator.startAnimating()
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), {
self.showPins(self.initialLat, locationLong: self.initialLong)
})
self.activityIndicator.stopAnimating()
You have to make sure you are doing these things:
1. Load all annotations at once
Apple recommends in the MKMapView Class Reference all annotations (pins) should be loaded at once:
When configuring your map interface, you should add all of your annotation objects right away. The map view uses the coordinate data in each annotation object to determine when the corresponding annotation view needs to appear onscreen. [...] Because annotation views are needed only when they are onscreen, the MKMapView class provides a mechanism for queueing annotation views that are not in use. Annotation views with a reuse identifier can be detached and queued internally by the map view when they move offscreen. This feature improves memory use by keeping only a small number of annotation views in memory at once and by recycling the views you do have. It also improves scrolling performance by alleviating the need to create new views while the map is scrolling.
2. Reuse your annotation views
You have to make sure you are correctly reusing existing annotation views by using dequeueReusableAnnotationViewWithIdentifier in the viewForAnnotation method.
3. Do heavy loading on background threads
If you can't fetch / load the annotations at once you could use Grand Central Dispatch to let the 'heavy' method run on a background thread so the UI doesn't block. Note: any changes to the UI from within that method (on the bg thread) will need to explicitly happen on the main UI thread.
Regarding backgrounding, take a look at this Stackoverflow answer:
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
print("This is run on the background queue")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("This is run on the main queue, after the previous code in outer block")
})
})
Or alternatively, use this 'Async' library for some syntactic sugar.
Related
I want get data from url, but until process complete, i want show loading icon
I use this code but not work for me
DispatchQueue.global(qos: .background).async {
// show loading icon
// start get data
DispatchQueue.main.async {
// remove loading icon
}
}
From UIView
Threading Considerations
Manipulations to your application’s user interface must occur on the
main thread. Thus, you should always call the methods of the UIView
class from code running in the main thread of your application. The
only time this may not be strictly necessary is when creating the view
object itself but all other manipulations should occur on the main
thread.
Which means you should probably show/hide and manipulate therefore your View in the Main thread and not in a background thread as you are doing in "show loading view".
class HealthViewController: UIViewController {
var foods: [Food] = FoodUtils.getFoodList() // some expensive operations
var fruits: [Fruit];
override func viewDidLoad() {
super.viewDidLoad()
self.fruits = FoodUtils.getFruitList() // some expensive operations
}
}
I wonder for above class in iOS/Swift,
When FoodUtils.getFoodList() is prepared on runtime?
What is the good practice? preparing list inside viewDidLoad or in class scope? Which lifecycle of UIViewController will effect the memory on runtime for both cases?
In the code (object initialization):
var foods: [Food] = FoodUtils.getFoodList() // some expensive operations
the expensive operations are performed when the view controller instance is created.
in the code (inside the viewDidLoad):
self.fruits = FoodUtils.getFruitList() // some expensive operations
the expensive operations are performed once the interface elements (IB outlets) have been hooked with the viewcontroller, and the views have been loaded.
In practice it doesn't make a difference because viewDidLoad is performed after the class has been initialized WHEN USING SEGUES (for programmatically shown VCs read the note at the end).
If you are talking about an operation that can take several seconds, then the best practice would be, to perform the expensive operations BEFORE showing the view controller while a busy view (a view with an activity indicator) is shown.
Alternatively, you could do it in the viewDidAppear method, and start the View controller with an activity indicator shown, then when the expensive operations finishes, hide the activity indicator and load your data.
As a matter of fact, the second approach is used very commonly, specially when showing big lists of data. You must have seen it when using apps that start with a spinning indicator until the data is ready to be displayed.
Note:
You can separate the timing of the 2 functions if you are showing your view controller programmatically, since the first one is performed when you use the "load from nib" method. While the second one is performed once you actually try to access any views inside it.
Note 2:
Expensive network operations should always be performed on background threads so that the UI is not blocked. Which is why people often show activity indicators while info is being retrieved in the background.
I am working in Swift. When a user presses a UIButton it calls a function ButtonPressed(). I would like ButtonPressed() to do two things:
Update the UIView by removing the current buttons and texts, then uploading some new text.
Call function TimeConsumingCalculation(). TimeConsumingCalculation is the complicated part of my app and does some calculations which take about 20 seconds or so to complete.
Right now, I have the code in the basic order:
ButtonPressed(){
self.Button.removeFromSuperview()
TimeConsumingCalculation()
}
However, it will not remove the button or do any other UI updates or additions until after the TimeConsumingCalculation is complete. I have read and attempted a few guides on closures and asynchronous functions, but have had no luck. Is there a special property with UIView that is causing it to be updated last?
As a side note - I have already attempted putting all UI actions in a separate function and calling it first. It doesn't work. The time consuming function does not take any variables from the buttons or UI or anything like that.
Thanks!
It seems like timeConsumingCalculation() is blocking the main queue, which is in charge of UI updates. Try calling it like this instead and use the isHidden property to hide the button instead of removing it from the view completely.
ButtonPressed(){
self.Button.isHidden = true
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
self.timeConsumingCalculation()
}
}
here you call timeConsumingCalculation() asynchronously on a background thread. The quality of service we give it is userInitiated, read more about quality of service classes here
How do I get the UIActivityIndicatorView to display first, then execute other code?
I've experimented with using sleep, and it works but it doesn't "feel" right and adds an extra second to processing a bunch of core data stuff. I've also tried dispatching it to the main thread which only works some of the time. (I'm guessing when the rest of the block is executed outside of the main thread).
Ideally as soon as a user touches the button the instance of the UIActivityIndicatorView would display (which seems to happen where I've used it in other apps by itself or with other minimal processing).
Details: I have an IBAction connected to a button that executes a bunch of core data stuff, sometimes including images, that takes between 1 - 3 seconds to finish. When it finishes it dismisses the view controller. The view controller where this is executed is presented as a modal over current context.
Here is a code snippet:
// get the background queue
let bg_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(bg_queue, {
// long running code here...
dispatch_async(dispatch_get_main_queue(), {
self.activityIndicator.stopAnimating()
})
})
I have a table view, and when the user selects a row, i push them to a new ViewController. At first, I initialized all my view objects in the ViewDidLoad method (involving web service calls) but I saw that it made the transition from my tableview to my new viewcontroller very long.
Instead, I moved most of my UI initialization in the ViewDidAppear method, and I like that it sped up my transition from tableview to new viewcontroller.
However, I cannot press any buttons in my NavigationBar at the top of the screen (like the back button) until my ViewDidAppear method completes and the UI is loaded.
What's the solution for this? Is there another way for me to load my UI without it preventing the user from interacting with the buttons in my NavigationBar?
Thanks!!
you do too much on the main thread. off load your longer operations like IO or longer computations BUT take care to not mess with the UI in the background thread.
Only touch the UI on the main thread. (Note sometimes it might seem safe, but in the long run it always end up producing weird issues)
one easy way is to use GCD:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
//insert web service requests / computations / IO here
dispatch_async(dispatch_get_main_queue(),^{
//back to the main thread for UI Work
});
});
You could use grand central dispatch to make your web service calls asynchronously, which will keep the UI on the main thread responsive.
//create new queue
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.siteName.projectName.bgqueue", NULL);
//run requests in background on new queue
dispatch_async(backgroundQueue, ^{
//insert web service requests here
});
Here's a more in-depth tutorial:
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
Try to initialize your UI in the background by using the following method
[self performSelectorInBackground:#selector(initYourUI) withObject:yourObj];
You can call this in the ViewDidLoad