I have a mathematical program for the iPad using a Big Number library that does calculations, and then updates an array of up to 20,000 UILabels in a UIView (myView) based on those calculations.
The calculations can take about 5 seconds, during which time the backgroundColor of each of the UILabels is set to a color. Because nothing is happening on the screen, I have a blinking UILabel inProgressLabel that informs the user that the system is calculating. I then call layoutIfNeeded, with the idea that when it is finished, the screen will have been updated. Finally, I turn off the blinking UILabel.
Here is the pseudo-code:
inProgressLabel.turnOnBlinking()
for row in 0..<rowCount
{
for col in 0..<colCount
{
// perform some calculation
let z = buttonArray[row][col].performCalculation()
//now set the Label background based on the result of the calculation
buttonArray[row][col].setLabelBackground(z)
}
}
myView.layoutIfNeeded()
inProgressLabel.turnOffBlinking()
My understanding was the layoutIfNeeded() is synchronous. Thus, the screen will update and then, and only then, the blinking inProgressLabel will be turned off. However, the inProgressLabel is actually turning off immediately after layoutIfNeeded is called, and then it can take another five seconds for the array of UILabels to update.
I thought that maybe this is happening because the updating is occurring in a different thread. If this is so, is there a way to know for sure when the UIView, and the array of UILabels, have finished updating (displaying), so that I can then turn off the blinking UILabel?
Many thanks in advance.
You can try
let dispatchGroup = DispatchGroup()
for row in 0..<rowCount
{
for col in 0..<colCount
{
dispatchGroup.enter()
self.doCalcAndUpdateLbl(index: i) { (finish) in
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
myView.layoutIfNeeded()
inProgressLabel.turnOffBlinking()
}
UIKit controls are not thread-safe. Updating UILabels from the background produces undefined behaviour.
layoutIfNeeded performs any pending layout tasks and returns. It has no magical foresight of any changes you may be planning to make in the future.
You need to schedule your work to occur off the main queue, and ensure results are pushed back to the main thread when done. If it makes sense to dispatch various calculations to occur separately, use a dispatch group as Sh_Khan suggests. Otherwise consider just performing the background calculations in a single block and hopping back onto the main queue from there.
It's also highly unlikely that 20,000 UILabels is appropriate; look into using a table view or a collection view. As a general rule, you should try to have active controls only for whatever is presently on screen, and both of those views offer ways very easily to manage that active set. Every view has a memory footprint because the content of views is buffered — you'll hurt your own performance and that of other running applications if you unnecessarily squeeze memory.
Related
I have this small view V
v: UICrazyView
which has sundry animations, which run often, and which follow various annoying states and inputs.
Now on top of that, from time to time the whole thing is just hidden
var slideAwaySomePanel: Bool {
didSet {
.. do many other things
v.isHidden = slideAwaySomePanel
}
}
It might be hidden for a minute, an hour, forever, or never.
It occurred to me, while V is hidden ... are the animations still running?
Do they still use a lot of battery/performance?
I was about to override isHidden and start writing a whole lot of fragile PITA code that would know what to do as isHidden is toggled, but maybe that is pointless.
I wish to know
When you isHidden, do all the calculations (and even drawing?) continue for the ongoing animations? Are you still using battery? Should we carefully stop everything during isHidden to save battery / performance. Or, does isHidden stop everything anyway? Or does it still do the timers and curves, but not waste any power on drawing?
Do all the timers and so on actually "pause" when you go in to isHidden? If you have an endless repeating animation, or, a 10 second fade or such, does it "hold" at an exact position, and continue when you not isHidden? Or what happens?
In short should we carefully and tediously stop by-hand animations, when isHidden is in effect? Or is there no point?
(It occurs to me, this is very much like in cg where you either do or don't have animations or other physics ongoing when objects are occluded or out of the frustrum. For this reason game engines simply have a toggle to decide on exactly that behavior "keep going or not when I'm offscreen?")
I'm pretty sure, even though I have no reference, that hidden views are not animated because Core Animation was implemented very efficiently in terms of performance.
Core animation layers and animations have their own clock. The animation state is calculated from this time. The clock continues to run when the view is not visible. Since neither the layer nor the animation object are destroyed by hiding the view, the animation has exactly the same state after reappearing that it would have had if the view had not been hidden.
Apple gives some nice examples how to modify the animation timing for some use cases.
Problem
scrolling to the bottom of table view is slow
the table view contains about 25 items
and there is a bit of complexity in creating each cell
so creating 25 of them at the same time takes about 200 ms(believe me there is no way I can make it any more optimized)
Soloutions I've tried
there are basically two options for scrolling to bottom of table view(And I have searched the entire stack over flow for this)
tableView.scrollRectToVisible OR tableView.setContentOffset
tableView.scrollToRowAtIndexPath
the problem with the first option is, although it is pretty fast and it doesn't recreate every cell in the way from top to bottom of tableView, I need to call a new main queue to get it to work. if I call it in the same queue as the one that called tableView.reloadData(), then there will be a strange lag when scrolling the tableview(logs show that lots of cells are being re created unnecessarily, and I mean lots of them). and let me add that both threads are main thread. but still I must call the dispatch_get_main_queue() to get rid of that awkward lag. however new main thread adds a good delay to the initial load.
here is my code:
func loadData(groupID: String){
self._chats = self._cache[groupID]!
_tableView.reloadData()
dispatch_async(dispatch_get_main_queue()) {
let rect = CGRect(x: 0, y: self._tableView.contentSize.height - self._tableView.bounds.height, width: self._tableView.contentSize.width, height: self._tableView.bounds.height)
self._tableView.scrollRectToVisible(rect, animated: false)
}
}
again if I don't use the dispatch_get_main_queue() the tableView would be supper laggy. and if I do use it, then there will be about 100 ms delay in execution which makes the initial loading slow.
the second option is slow too, cause scrolling to the last indexPath means creating every cell in the way. and that cannot happen in the background and doing it in the main thread means freezing the UI for about 200 ms.
can you suggest any other solution or find something wrong with what I'm doing? I've tried every thing and still tableView isn't fast enough.
You can use Time Profiler one of Xcode Instruments to understand on which of your processes your app stack.
I am building a fairly simple iOS app that, when it boots up, loads a few views and stacks them on top of each other.
However I am experiencing a very long lag between when the app boots and when the views appear. These views are not complicated and take very little time to build.
At first I thought it might have been the image causing the delay, as I grab it via a URL. But after logging some checkpoints, the image load happens fairly quickly.
I did similar checkpoint logging when builidng the UIView and again, it's fast.
So finally, I checked to see if there was a gap between when the call to .addSubview is made and when the views actually appear, and in fact there is.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let returned = feedMngr.retrieveFeed()
if (returned) {
self.constructFeedStack()
self.view.addSubview(self.firstCardView!)
self.view.insertSubview(self.secondCardView!, belowSubview: self.firstCardView!)
self.view.insertSubview(self.thirdCardView!, belowSubview: self.secondCardView!)
self.view.insertSubview(self.fourthCardView!, belowSubview: self.thirdCardView!)
NSLog("fin")
}
}
After "fin" is logged (roughly 5 seconds after app launch), there is a 5-10 second lag before the views are actually presented.
Anyone know why this may be the case?
You're modifying the view hierarchy off of the main queue ("in the background"). This isn't allowed. Your UIKit calls all need to be on the main queue.
I have the problem that I have some really big UIScrollView and tons of images loaded on it as user scrolls. Images are stored on the device, however I receive information from server what to display on particular part of UIScrollView. When user scrolls a bit I need to show images at new position as I cannot afford to draw whole UIScrollView with images at startup. For the background I had one relatively small image which I move throughout the View. But the problem is that on top of that background I should draw a lot of UIImage objects(about 300-400) which are not particulary bih however are separeted on layers(one image on top of other on top of other etc.). Blocking scrolling while drawing is NOT an option.
Now I'm trying to decide which approach will suite my best:
Add all needed images to UIView on background thread and then just add UIView to ScrollView on main thread(which hopefully wont take long). Here when scroll somewhere I will need to calculate and create new UIView with objects and position it next to existing and eventualy to remove first UIView with all objects and layers when user continues to scroll in some direction.
Combine all layers in image with CoreGraphics and present them as objects with already decided layers. In this way I can remove specific object(image) from scroll view. When user scrolls I just create new objects and add them to view as full objects, and can remove objects when user scrolls enough instead of removing whole view. The problem here is adding multiple objects to UIScrollView on main thread, however when they are combined they won't be more than 15-20 objects.
My biggest concerns are performance and threading. As I said I cannot block main thread(or let's say cannot do this for a time that user will notice) and cannot combine images at my graphics department as they have tons of variatons which are decided at runtime. That's why I'm thinking of a way to prepare data on background thread and just really fast add it on main thread instead of preparing and adding it on main thread(which will block UI).
Every help will be greatly appriciated!
Regards,
hris.to
Look at using CATiledLayer for a UIView backing. It is was designed for this.
I have a map that has one UIView in a UIScrollView and the UIView is sized to the full size of the entire map. The CATiledLayer handles when to draw each tile of the view.
Ok, so I'm writing here just to let you know how I fix my issue.
Main problem was that I was moving a background picture while scrolling(so I don't load an enormous file) and while doing that I was fetching information from server and try to draw on the same tiles which makes system crash with well known crash:
CALayer was muted while enumerated
I was depending on performSelector method along with #synchronized but it turns out that this is not effective and sometimes two threads(main UI thread and background thread) were trying to change same tiles on screen. So basically what I did is to change background fetching and drawing from:
[self performSelectorOnBackgroundThread]
to using concrete background thread, to which I store reference:
#property(nonatomic, strong) NSThread* backgroundThread;
And now each time I need to load new tiles or I need to move background, I'm cancelling this thread(and make sure it's cancelled) before start any other operation. Also there was a little problem when switching between view with this thread and other views, as my thread hangs, and I needed to set a timeout:
[_backgroundThread cancel];
int currentAttempts = 0;
while( [_backgroundThread isExecuting] )
{
if( currentAttempts == MAX_ATTEMPTS_TO_CANCEL_THREAD )
{
//make sure we dont hang and force background
_backgroundThread = nil;
return;
}
[_backgroundThread cancel];
currentAttempts++;
}
In my 'scrollViewDidScroll' however, I didn't use the recursion as this results in slight UI blocks while scrolling on older devices(such as iPhone 4) which is unacceptable. So there I basically just cancel the thread and hope to get cancelled quick enough(which with dozens of tests appears to be working):
- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
[_backgroundThread cancel];
//move background around
[self moveBackground];
}
Downside of this approach is that you need lots of check in your background thread as calling 'cancel' to it won't actually cancel anything. As per apple docs it'll only change isCancelled state of your thread and you are responsible to make this thread quit in basically the same way as it'll quit normally(so the system has a chance to cleanup after your thread):
if( [_backgroundThread isCancelled] )
{
return;
}
Regards,
hris.to
I have an app that fetches calendar events and displays data to the user. I'm getting some weird behavior when trying to update my labels.
I can fetch the calendar data just fine but when that gets done, my problem is that according to NSLog my label.text property has already changed, but it's another 4-8 seconds before the view gets redrawn.
Therefore, I'm trying to detect when the label gets redrawn, not when it's .text property changes so I can hide a progress view at the same time the data is populated in the labels.
I have already tried setNeedsDisplay and setNeedsLayout on self.view and the labels themselves. after the .text property of the labels has changed - doesn't work.
So unless I'm completely missing something about using setNeedsDisplay (which I understand only updates on the next redraw anyway), my question is, how do I detect when the UILabel and/or the UIView redraws itself?
How my app is setup:
I've been stuck on this for about 3 weeks.
Make sure setNeedsDisplay is being called on the main thread, using performSelectorOnMainThread:withObject:waitUntilDone:, for example:
[view performSelectorOnMainThread:#selector(setNeedsDisplay)
withObject:nil
waitUntilDone:NO];
Quote apple develop document :
The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.
maybe your main thread are blocking by other things , such as deal with many complex calculations
eg:
- (void)testMethod
{
myLabel.mytext = #"aaaa";
[myLabel setNeedsDisplay];
// some complex calculations
// the quickest , it will run finish the method then redraw.
}