So I have an app, in which I use custom UIView. There are other UIView, which are subviews of that view. All of them have pan gesture recognizers, so that the user can pan them in the specific fashion. When I test my app using one such view all is well and good, it is all smooth and fps is around 55-60. However, when I add the second one(somewhat smaller then the first one, and kinda inside first one, but not as a subview). The FPS of the outer views drops to 20-30, while the inner one(which is smaller) works in 60 fps. This two views are ABSOLUTELY independent, meaning that interacting with one doesn't affect the other one. In addition to that, I checked running time on the interval of one second, the results are the following:
For 1 view: 153ms
For 2 views 145ms
For 3 views 150ms
The calculations that are performed on each view are ABSOLUTELY identical in complexity. Why does one work smoothly, but when I add the second one it starts to lag, while the execution time stays the same ? I would appreciate if somebody could hint me at how I should debug it.
The most consuming operation according to the Time Profiler is repositioning one of the views:
view.center = newCenter
I simply can't understand why FPS drops 2-3 times, while the running time stays the same. What should I look for ?
UPDATE:
So I found the source of the problem, when one view is inside the frame of the other the whole thing is being constantly redrawn, even though no changes were made to the layout of the inner view. So when I positioned a few of them in different parts of the screen everything was fine, but when I put one "inside" the other things get messed up. Inner one works quickly, while the outer one lags. "Flash updated regions" flag in core animation show that when I interact with outer view, inner view gets updated as well. Any suggestions on what I might need to do, because I need that layout when they are one inside each other. So how can I prevent(lock) other view from redrawing ?
Thank You.
Related
I am displaying an array of movable cards (UIViews) in viewController.
This cards have each set a pan Gesture recogniser that allows them to be moved.
When I move a card up self.removeFromSuperview() is called and the view is removed from the viewController's view.
The problem is that since I have displayed many cards one in top of the other, the first one I move around lags a lot and drops some frames.
After some views are removed tho, the user experience improves and It doesn't drop frames anymore.
I was wondering If there was a limit in how many panGestureRecongizers there may be concurrently.
To investigate more what was causing the issue, I tried to use the Time Profiler tool to see what may cause this traffic on the main thread and this is what I found out:
To be honest I'm pretty new to time profiler tool but what I think I see it's causing the problem is indeed the panGesturerecogniser method.
Is this possible or may I search on another path?
So this is a rather simple problem that I've had a rather hard time fixing. I'm trying to take advantage of table views for displaying a huge list of cells because it is already optimized for cell reuse and saves me time in that regard. I need this list to smoothly scroll at an accurate duration. If I scroll too fast cells are still loading when they show on screen so I'm only scrolling one cell at a time.
The code I'm currently using is modified from here: https://stackoverflow.com/a/17956396. I've changed it to only add a single animation to the array and to never remove it. Instead I have a counter that checks to see if there is another cell before scrolling.
This is working fine, but my issue is that I can't update the animation duration to be faster or slower to keep an accurate pace. Is there a way for me to chain animations in a way that would allow me to dynamically update the duration so as to keep accurate timing?
I'm seeing some performance degradation in my application after some time and I'm trying to figure out what's going exactly.
I have a complex view controller (VC1) which contains scroll view, few table views inside, some custom cells with horizontal scrolling and custom drawing etc.
After several (around 10) refreshes of all these objects (reloading tables, reposition subviews etc) when I try to call presentViewController to push another view controller above VC1 I can see about 2 seconds delay between viewWillDisappear and viewDidDissapear
I tried to profile the app to see if there are memory leaks but couldn't find any. Memory usage grows when view refreshes and switches between different modes, but then it become more or less stable in around 30m.
Works fine in Simulator, but visible slower on iPhone5. And this slowness is visible only when I try to switch from that view controller.
I ran a profiler and recorded where these 2 seconds are spent. Here is link to trace file: https://dl.dropboxusercontent.com/u/6402890/trace.trace.zip
Majority of the time spent by UIKit doing layout as I can see.
What can I do to optimize it? Is there way to take may be a snapshot of a view and use it for "leaving view" animation and restore view hierarchy when we're coming back?
UPDATE: Adding screenshot for the profiler (click for full resolution):
UPDATE2:
After analyzing output from recursiveDescription I can see the following:
In the easiest case I have ~200 lines in the output. And performance is ok.
When I switch to more complex scenario hierarchy of views growth to ~500 lines, but still performs ok.
After multiple refreshes this number goes to ~2000 and this is where it become slow. Analyzing output with 2000 views I can see that ~1500 of them belong to hidden cells that are not even displayed in this mode anymore. When I'm refreshing table views cell types change too, and I'm utilizing different cells, but why the cells that are not used anymore are still being in subviews of table views?
Any recommendations?
From your stack, I suspect you've added a large number of views you didn't mean to add. Since it's related to reloads, I would check your reload logic and make sure it doesn't re-add all the views in your hierarchy without removing the previous views. You can write a quick debug routine use -recursiveDescription to recursively walk the -subviews of each view and print them out to see what's in the hierarchy.
It's possible that your issue is in the layer hierarchy rather than the view hierarchy, but the symptoms you describe make me think views.
EDIT: From your update, you probably have one of two things going on. Most likely, if these are actual UITableViewCells that shouldn't even exist anymore, then you have a retain loop somewhere. Alternately, your cellForRowAtIndexPath: may be incorrect and may be adding new views to an existing cell when it should just be reconfiguring the cell.
In either case, though, 200 views seems a lot of views for a "best case." You may be overusing views in places that you should be doing custom drawing. If the performance is ok, then… ok, but I'd test carefully on your slowest supported devices.
From the Instruments Time Profiler output, you can see that NSISEngine is eating up a ton of CPU. That class is responsible for doing the Auto Layout constraint evaluation and layout calculation.
So it looks like you are using Auto Layout, at least for some of the views.
Are you by any chance removing and re-adding constraints anywhere at runtime? I've seen this exact problem caused by that (can explain more if this is relevant).
If you aren't removing constraints, it sounds like you might have a fairly complex view hierarchy, and if you're using Auto Layout throughout, it's likely that you have a lot of constraints. As you may know, Auto Layout degrades in performance pretty quickly above a certain point due to super-linear time complexity of solving constraints. Check the output of po [[UIWindow keyWindow] recursiveDescription] from the debugger to see what your view hierarchy looks like.
I'm not sure what your view controller transition looks like, but you could try removing the disappearing view controller's view from its superview before doing the present. That should prevent it from doing layout calculations as it transitions. If that solves the performance issue, you could quickly snapshot the view hierarchy and then replace it with a single new UIImageView of the snapshot to display during the transition animation.
(One final thing: are any of your table views using Auto Layout in their cells? do any of these table views have more than ~20 cells?)
When presenting another controller your original controller has to be animated out of the window, which causes the view's frame to change and probably triggers all layoutSubviews methods and your manual adjustments.
You could try to avoid this by deactivating autoresizesSubviews in viewWillDisappear.
It is not very clean, but then probably all the calculations you are making may not be as well!
Try to optimize them:
Don't call layoutSubviews directly and call setNeedsLayout only if really needed.
Try to replace your manual resizing code with autoresizingMask or autoLayout.
Adjust views lazily and only if they are visible and if their size and not origin really has changed.
Don't reload your tables entirely but try to change only individual rows.
Try to use only a single table view.
Make sure to reuse cells.
The problem may have something to do with that you are placing UITableView instances inside a UIScrollView. That is explicitly prohibited by Apple's documentation for UIWebView (surprise!):
Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behaviour can result because touch events for the two objects can be mixed up and wrongly handled.
I suspect that may also mess up table view cell reuse mechanism. Anyway, I'd also recommend to check if you are not 'leaking' any views at all. Keep in mind that even invisible views participate in layout if they are in the view hierarchy.
Edit: in response to Update 2
It's evident that cell reuse mechanism is not functioning properly. Try to make sure you are using correct instances of UITableView when dequeueing table view cells from reuse queue (check your data sources).
Ok. I HAVE seen a LOT of topics which are similar to what I'm trying to do here, but nothing that quite matches. Maybe my approach is way off base. Here's the deal.
I'm drawing a chart to my UIView subclass in drawRect:. I then use a UIPanGestureRecognizer to translate the chart's offset (internal, not in the view hierarchy) to the right/left. This works great, but my problem is that the recognizer's action gets fired with relatively low frequency. Specifically, the resultant panning is MUCH choppier than, say, in a scrollview or tableview. Particularly when the number of points in my chart goes up. Now, what I'd LIKE to do is not actually trigger drawing from the pan gesture's action, but have a timer firing at something like 30 times per second, interpolating the offset from the moment the pan fired to the current moment. However, this is getting more and more complicated.
Anybody have suggestions? Should I really just be doing this with a UIScrollView? The reason I'd prefer not to is that the data may extend quite far in either direction, and I'd like to not have to draw (or at least store) the whole path at once, but just do the section which is currently displayed. Should I be using CAShapeLayer? I'd like some input before I spend another day playing around with these possibilities...
Any input / experiences would by most welcome.
So, in the end, I DID end up putting these things in a UIScrollView. I had to do quite a bit of work to get this up and running, the complicated part being the creation of a series of PANES which would be added to the scrollview. Each pane asks a delegate for the data it needs to draw, and then they're all stitched together. Fortunately, everything worked great once I put these scroll views containing chart panes into a table view. Still haven't tackled scaling, but I've seen a few posts on here which should make it relatively trivial.
So that's the answer to my original question. DON'T try to pan and draw on each frame, it WILL be choppy, and baring some real hack, there's no good way I could find to smooth it out. Scroll view handles all these problems for you, as long as you can split your view up nicely. I guess I could have used one long view, but then when the data set gets large I'm in trouble.
Now I've got a related problem though -- when offscreen charts scroll into view (they're in UITableViewCells), there's a big hiccup as the new chart gets drawn. I think I will try drawing them into an image asynchronously, so that when the new row scrolls into view, I've already got the image.
Greetings! I'm trying to borrow the view flip concept from Apple's TheElements sample app. This sample employs a container UIView in which you can swap between two subviews. The flip is achieved using setAnimationTransition:forView:cache: and removing/adding each subview.
In general, the flip works and I can swap between my two views (a UITableView with headers/footers, and a MKMapView). However, various (consistently chosen) areas of my table view are obliterated (using the table view's background color) before and after the table view is flipped, and I don't understand why.
I can't find anything unusual about the table view in terms of drawing. In the case of the sample app, the view is drawn from scratch, but I would hope that doesn't factor in to it! I'm really hoping it's something simple - maybe a UITableView property setting?
Clues appreciated. Thanks!
Update: When I slow down the animation, I begin to get an understanding of what is being disturbed (though I still don't know why). My table header view contains an image view and label view. Now, imagine the CGRects for those two views (only without any visible content, just a background color) being redrawn further down the table view, right over the table rows.
This also happens with another chunk of real estate that appears to come from a table view cell that was set with a custom height (to size the text within it).
I dropped a gratuitous number of breakpoints throughout my code (where things are sized, created and whatnot), and not one of them is hit during the transition.
I even tried placing my table view inside a UIView and targeting that for the transition instead. No difference.
From the API docs:
Caching can improve performance but if
you set this parameter to YES, you
must not update the view or its
subviews during the transition.
Updating the view and its subviews may
interfere with the caching behaviors
and cause the view contents to be
rendered incorrectly (or in the wrong
location) during the animation. You
must wait until the transition ends to
update the view.
That's all well and good, but I'm not updating any views or subviews during the transition. (At least not on purpose! Again, see the note about the breakpoints. Nothing got hit.)
Amazing.
Answer: In this case, it appears to only happen in ... the Simulator! On the device, it's fine.
I was thinking that such a run-of-the-mill transition would be the sort of thing that would render identically on the Simulator and the device.
I was wrong. So if you ever see animation transition glitches on the simulator, hang in there. It might be fine after all.