Swift - UITableView scroll to bottom is slow - ios

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.

Related

Swift How to know if layout finished after layoutIfNeeded()

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.

iOS freezed UI without error logs

I have an issue on my swift iOS app and i'm not able to understand which is the real problem. I have a UITabBarController with five UINavigationControllers on each tab. Main content of these controllers are UICollectionView filled dynamically from a WebService with custom UICollectionViewCells (about 30 for each CollectionView) with custom UIImageViews, custom UILabels and custom UIButtons loaded from custom xib files.
When i push few controllers all is working good (CollectionViews become a bit slower but i think it's normal for memory usage) but when a NavigationController contains 6 or 7 controllers and i try to scroll down, my UI is freezed, the app doesn't crash and i don't receive any kind of runtime error. I can still exit using Home button but the app is completely locked.
I'm dequeuing every cell in every collectionView with identifier and i'm using this attributes in every custom cell for performance:
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen().scale
What i tried
Debug views too see if some UIWindow or UIView is covering the controller view
Stop image loading and show blank imageViews
Clear code used in UICollectionView scroll delegate methods
But the issue is always reproducible on 6th or 7th controller, making some stress test. I gave a look to FPS too, but values are always from 40 to 60 that seems good. CPU usage is always very high, 90/100 % but method didReceiveMemoryWarning is never fired.
My goal would be that my app could push many controllers without freezing or become very very slow, but i can't understand what i'm missing.
Thanks to Philip Mills
Issue is that i was calling this code in my custom UICollectionViewCell setup:
func rotateWithDuration(duration: NSTimeInterval, indeterminate: Bool) {
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: {
self.transform = CGAffineTransformRotate(self.transform, CGFloat(M_PI))
}, completion: { finished -> Void in
if indeterminate {
self.rotateWithDuration(duration, indeterminate: indeterminate)
}
})
}
View rotation animation was running indefinitely in every cell, so the CPU was a bit sweat.

Unintentional lag from when program executes .addSubview to when view actually appears- Swift

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.

iOS slow to release images while scrolling (no tableview)

Here is the problem :
I am writing an app which displays some pictures, with a treemap layout (for an example, see https://raw.github.com/beckchr/ithaka-treemap/master/Core-API.png)
This layout is displayed in a UIScrollView. Since many pictures can be added to that scrollview, I want to release the ones which are not on currently on screen. I am not using ARC.
At my point, I know which pictures I should release, and how to do it while scrolling (calling some "unload" method). There is no useless call of that method. The problem is that, when pictures are released, the scrolling stops for a little moment (a few ms, but this is enough to be bad looking, making the scroll kind of "jumping" and slow, not smooth at all).
What I've tried (put in the body of my "unload" method) :
imageview.image = nil
performSelectorInBackground:#selector(effectiveUnload) withObject:nil
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0,^(void){
dispatch_sync(dispatch_get_main_queue(), ^(void){
imageview.image=nil}
}
I think this problem is weird, since there is absolutly no slowing effect with memory allocation, but only with memory release.
Thanks for help, don't hesitate to ask for more information.
did you try removeFromSuperview to remove the imageview from scrollview
Don't add more and more UIImageViews - recycle them!
To save as much memory as possible you should follow the UITableView way of recycling views:
1. Once a view has left the visible area, add it to a "views pool" (and remove it from its superview. It's not an expansive operation!)
2. When a new view becomes visible, first check if there's a view in the pool. If not, then create a new view.
I know my answer doesn't answer your question directly, but that's the way to go. Otherwise you'll run out of memory eventually.

What's causing the delay in my segues?

One of my views freezes up for several seconds when I tap the back button.
In addition, when I tap on one of the items in this view, it shows a popup (custom, not a UIPopoverController). This popup appears quite fast, but when I "flip" the popup to see it's back side, the same long delay occurs.
I suspect the reason has something to do with the complexity of the view. As you can see in the screenshot below, it's a collection view, it has a background and some of the subviews are rotated (UIViewEdgeAntialiasing is on).
I used the Time Profiler in Instruments to figure out what's going on, but I'm stuck.
I don't see anything useful unless I deselect "Hide System Libraries":
If I look at the method names, I think they are related to auto layout. That suggests that it's trying to render something during the segue. But methods such as cellForItemAtIndexPath are not called.
There is also an iPhone version of this app where I don't experience this problem at all. It uses a tableview in stead of a collectionview. It also has a background and rotated pictures.
I took these measurements using the simulator; on my iPad Mini the situation is worse; it can take up to 20 seconds before the animation starts.
Update - Things I've tried thanks to your answers:
turn off UIViewEdgeAntialiasing : no effect on performance
I think this might be due to the UIViewEdgeAntialiasing flag. It seems that your main view (the one with lots of slightly rotated pictures) have lots of antialiased edges and hence is very taxing on the iPad's GPU. The fact that the drawing performance slows down when your popover is spinning (ie when the background is showing again) gives this some credence.
Try turning it off and see if the performance improves. How does it look like?
Rotation was the bad guy here. Each UICollectionViewCell has a UIView as a container view and within that is a UIImageView. I rotate it like this:
container.transform = CGAffineTransformMakeRotation(M_PI * someRandomFloat);
Remove that line and everything is snappy.
I use the same technique on the iPhone, but apparently this kind of rotation has less of a performance impact in UITableViewCell than in UICollectionViewCell.
I tried subclassing UICollectionViewFlowLayout to rotate cell itself in stead of one subview. Unfortunately that causes a similar performance issue.

Resources