I have a custom view within my ViewController that I want to update some info on. This info is being drawn asynchronously from Firebase.
I'm trying to use view.setNeedsDisplay or view.setNeedsLayout to update the view. But since each only changes the view after the next drawing cycle, how can I force the next drawing cycle to happen without forcing the user to leave the ViewController and come back?
Calling setNeedsDisplay or setNeedsLayout is sufficient to trigger “the next drawing cycle”, if you call it on the main thread. It sounds like you might not be calling it on the main thread. If you're not sure which thread you're calling it on, dispatch back to the main queue:
dispatch_async(dispatch_get_main_queue()) {
self.someView.setNeedsDisplay()
}
Related
This is more a conceptual query than coding. I have a custom activity indicator, a custom view. The only public API, the user will have is the init(onFrame frame: CGRect), startAnimating() and stopAnimating().
So, I want to know in the startAnimating method, should I create a thread whether main or DispatchQoS to run the animation.
Also, if I don't put the animation code in a thread, will it be automatically running on main thread?
All communication with a UIView must be on the main thread. All Core Animation is automatically performed on a background thread. So do not do any explicit multithreading in connection with CABasicAnimation.
When I present a view controller or perform a segue in swift should I call it in DispatchQueue.main.async {} or is that a problem. My problem is that should I run it in the background thread or the main thread. If I load data from the database should I also present the view in DispatchQueue.main.async {} or should I run it in the background thread.
You should call all the UI related transition, changes and updates on the main thread. But where should you use DispatchQueue.main.async {} ?
It is to be used when the call is made from a background thread. Example,if you are downloading data and parsing from an API, you usually do that in a background thread, once that is completed, maybe you want a UI transition or update, So that update will take place in the main thread, and since currently you are on background thread, you require DispatchQueue.main.async {} to make the changes on UI.
All UI work should happen on the main thread, so unless you're on a background queue there is no reason to dispatch to the main queue. In the common case the segue is triggered from user interaction (e.g. a button press) which would already be on the main thread.
If loading the data for the view that is being presented takes a long time, then you can dispatch asynchronously to a background queue to load it and dispatch back to the main queue when the data has finished loading.
Since this is asynchronous, it will mean that the view will be without data from the time where it is presented to the time where the data has finished loading. This is something that you have to handle in your UI. Depending on your application, one example could be to displayed a loading indicator while the data is loading. Another could be to fetch or pass a minimal amount of data to display and load the larger amount of data asynchronously.
I'm using MBProgressBar in my app to display feedback whenever there is a call to a certain webService.
To do so, in the method "requestStarted" of ASIHTTPRequest, I call:
[NSThread detachNewThreadSelector:#selector(startLoader) toTarget:self];
Where startLoader is the method that pops the HUD.
Now, the thing is that whenever I call startLoader directly, the HUD gets displayed with no problem, but when I call the method using the detachNewThreadSelector thing (which is needed), the HUD is displayed but with no text label.
If I had to guess, I would say I need to force-refresh the component, but I don't know how to do that.
Anything having to do with the HUD will need to be done on the main/UI thread. If you are detach and put on a background thread, the HUD will likely never get those updates because your request will finish before getting back around to the main thread.
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
I ran into an interesting iOS problem today involving a CATiledLayer. This only happend on the device - not in the simulator.
My view draws in its CALayer via the drawLayer: inContext: delegate callback. This layer has a CATiledLayer-derived sublayer, which does its own drawing in an overridden drawInContext: method.
Both layers are rendering pdf content via CGContextDrawPDFPage(). (The CALayer draws a low res version, while the CATiledLayer sublayer draws hi-res content over the top.)
I ran into a scenario where I would be done with the view - would remove it from its superview and release it. dealloc() is called on the view. Sometime later, the CATiledLayer's drawInContext: method would be called (on a background thread), by the system. It would draw, but on return from the method Springboard would crash, and in doing so, bring down my app as well.
I fixed it by setting a flag in the CATiledLayer, telling it not to render anymore, from the view's dealloc method.
But I can only imagine there is a more elegant way. How come the CATiledLayer drawInContext: method was still called after the parent layer, and the parent-layer's view were deallocated? What is the correct way to shut down the view so this doesn't happen?
The slow, but best way to fix is to also set view.layer.contents = nil. This waits for the threads to finish.
Set view.layer.delegate to nil before releasing the view.
-(void)drawLayer:(CALayer *)calayer inContext:(CGContextRef)context {
if(!self.superview)
return;
...
UPDATE: As I recall, there were problems with this in older versions of iOS when it came to CATiledLayers, but setting the delegate to nil before dealloc is now the way to go. See: https://stackoverflow.com/a/4943231/2882
Spent quite a long time on this. My latest approach is to declare a block variable and assign to self in viewWillDisappear method. Then invoke the setContents call onto a global dispatch queue - no need to lock up the main thread. Then when setContents returns invoke back on to the main thread and set the block variable to nil, which should ensure that the view controller is released on the main thread. One caveat though, I have found that it is prudent to use dispatch_after for the invoke onto the main thread as the global dispatch queue retains the view controller until it exits its block, which means you can have a race condition between it exiting (and releasing the view controller) and the main thread block setting the block variable to nil) which could lead to deallocation on the global dispatch queue thread.