From an expense point of view, which option provides the best computational results when adding subviews to a UIView's hierarchy:
Scenario 1
In viewWillAppear:animated or willLayoutSubviews remove all subviews of the container view using [[aView subviews] makeObjectPerformSelector:#selector(removeFromSuperView], reset all pointers or properties and then (re)initialise all subviews and add them to the container view hierarchy.
Scenario 2
Initialize and add all subviews in the viewDidLoad method (called once only, thus only adding them to the view hierarchy once, then in the viewWillAppear:animated or willLayoutSubviews methods setting the constraints or frames for each subview.
Scenario 2 is much more faster than the first one. Creating and deleting views is expensive you should try to limit as much as possible.
If you are using autolayout setting the constraints can also be done in the viewDidLoad, and setting the frame is not recommended.
So the best is to create and add all the subviews in the viewDidLoad set up the autolayout constraints there and do not set the views frame, only the constraints in other methods if needed.
Similar to your condition there is another condition :
Take the situation where you need to present a view when the user taps a button. There are at least two approaches to this:
1.Create the view when the screen is first loaded and hide it; then when you need it, show it.
2.Do nothing until you need to show the view. Then, create the view and show it, all at once.
Each approach has its own pros and cons.
Using the first method, you consume more memory because you
immediately create the view which holds on to that memory until it’s
released. However, when the user does taps the button, your app will
appear more responsive as it only needs to change the view’s
visibility.
Taking the second approach will have the opposite effect; by creating
the view only when it’s required, you consume less memory; however,
the app won’t appear as responsive when the button is tapped.
Related
I have a UICollectionView that I want to show, but I may place a UIView overtop of it with a slightly translucent background. When that occurs I need to prevent the user from interacting with the collection view - they should only be able to interact with the view that appears overtop of it. To do that, I've made the view fill the collection view's bounds and that works well. However I noticed when users of VoiceOver use the app, if they tap on the view it will focus it but then if they swipe right to go to the next element it will focus the first cell in the collection view and allow interacting with it. How can I completely prevent interacting with the collection view for all users?
I've tried setting scrollingEnabled to false and also userInteractionEnabled to false for the collectionView but that didn't do the trick. The label I have within each cell is still accessible, therefore the entire collection view is accessible.
Looks like accessibilityElementsHidden is the property you want; should be able to set this to YES on the UICollectionView to hide that subtree. From docs:
You might use this property to hide views that are covered by the arrival of a new view. In this case, the hidden views might remain visible onscreen, but they are not the focus of the user’s actions.
...which sounds like a good match for your case.
If users really should be only able to interact with the view that is on top of it and no other view (including the collection view), consider setting accessibilityViewIsModal on the view that is on top.
To nicely see what accessibilityViewIsModal does in practice, consider seeing the excellent interactive Figure 1 at Adding accessible behavior by David Rönnqvist, in section "Implementing accessible modal views".
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).
Let's say I have a full-screen UIView that overlays the main screen when a button is touched, and then goes away when this overlayed view is touched. This UIView could either be added and removed from the current view using addSubview: and removeFromSuperview, or it could be added when the current view is initialized and then shown and hidden by accessing and setting the hidden property of the UIView. Which is generally faster and better for performance (or are they the same)?
I did try add imageView and try loop 1000000 times to hide and show in each loop and add remove in each loop. Result is hide and show take 1s to do 1000000 loop. And add remove take 3s. I do it in simulator :)
I'd bet show and hide will be faster. The other way requires object creation/destruction, and fiddling with subviews.
More importantly, I think show and hide will be simpler, and the fight against complexity is paramount.
As Clay says, showing hiding will probably be faster, but you would need sensitive instruments to detect the difference. It's going to be single-digit hundredths of a second at the most, and probably much less than that. You won't be able to sense the difference "by eye".
Thus what matters is other things, like what is the easiest to understand and maintain? One problem with making a view exist in the view controller and showing/hiding it at well is that the layout of the view covers the other contents of the view controller and makes it hard to manage.
You can create a second XIB (or an XIB that goes along with your storyboard) that has your view controller's class as it's "File's owner" and link up IBOutlets to the views you want. Then you load the view from an XIB when you need it, install it as a subview of your current view. Then you remove it from the superview when you're done with it. I use that approach a fair amount.
I have to implement a view controller (on iPhone, portrait only, full screen view) where the upper part of the view must have an horinzontal, paged scrolling behavior, potentially infinite.
I already used for similar purposes UIPageViewControllers, to take advantage of the datasource and delegate protocols, which are very helpul for manage memory and other stuff (keeping only 3 view controllers in memory, providing delegates to handle actions exactly when a transition is done and so on): so I think that in this case too this component is the best choice.
But here comes my problem. In the view I'm realizing, I have to let the user understand that he can swipe left and right to move to another view: a page control is not a good choice, since the scroll could be potentially infinite, so I would like to let a small portion of the views of the left and right view controllers to be visible.
Something like that:
link to the image (sorry I cannot include images in my posts yet)
Up to now I have not been able to figure out how to realize this. In the options during initialization, UIPageViewControllerOptionSpineLocationKey can be specified to set (from documentation) "Space between pages, in points": but this seems to work only with positive value, so that the space increases, while it ignores negative values that could reduce the space.
I hope there might be a solution using page view controllers, since at the same time I need to refresh a table view in the lower part of the screen when a transition is complete, and the delegate method of page controllers is ideal for this aim.
Maybe a collection view can be an alternate solution, but it is more complicated and I'm not sure how to obtain a behavior like the one I described to refresh the table view.
If needed I can attach some code and a screenshot of the prototype
Ok, I understand that this is not possible and why it is.
The datasource methods load, when needed, the view controllers that are before and after the current one. Making these view controllers' views always visible, as I desired, will require that the datasource loads more than one view controllers after (or before, depends on the direction of scrolling) the current one, to be ready for the pan actions (potentially, before the animation is ended by the user lifting up its finger, two view controllers "after" or "before" could become visible it my desired configuration), and this is not intended by UIPageViewController on iPhone, especially in portrait mode.
So actually, the only way to achieve that more than one view is visible in an horizontal-scrolling component at any time, is to implement a UIScrollview with horizontal paging and calculate the contentSize and other sizes accordingly.
I have a Master custom container view controller, whose purpose is to swap between two different view controllers that each manage a form with text fields. We'll call these FormVC1 and FormVC2. Both of these controllers's views are managed by a XIB.
I can successfully swap between FormVC1 and FormVC2 using:
transitionFromViewController:toViewController:duration:options:animations:completion: while passing UIViewAnimationOptionTransitionCrossDissolve for the options argument.
Now I want to sweeten the transition a little, and when FormVC1 is being dismissed, I want the individual text fields to fly off to the left (one at a time), and when FormVC2 is being presented, I want the individual text fields to fly in from the right, one at a time.
I'm a little confused as to how to set this all up, and where exactly to place the specific text field animation code. Do I put that in the animation block within Master? Do I put it in each FormVC's appearance callbacks?
You have a few options, so pick the one that works best for you:
1) Make the container view controller contain a scrollview, with scrolling disabled, and scrollbars disabled. Set the two form vc's as child vc's of the container, and add the views into the scrollview next to each other, so only one is visible. At the appropriate time, show the next form by setting the content offset of the scrollview with animated set to YES.
2) Even better (more easily extendible to add arbitrary number of forms), use a UICollectionView instead of a basic scrollview, set it to use the horizontal linear layout, make a custom cell to hold your form vc class (disable scrolling and scrollbars), then to show the next form, scrollToItemAtIndexPath:: should do the trick.
3) Most basic, maybe good enough: Make your container a navigation controller and just push the form vc with the default transition.
All these solutions assume your form vc's are only the controls you want animated and the main view has a transparent background.
In the animations block of transitionFromViewController:toViewController:… change the frames of the elements that you want to be animated. To make it fly off to the left, subtract something like 200 from the frame.origin.x. To make them fly in from the right, start at a high x value (maybe 500) and animate it to the center.
To animate them sequentially, you could use different x values for each element. For something more complex, use animateWithDuration:delay:options:animations:completion: and set up the next animation in the former's completion block. You'd use that instead of transitionFromViewController:toViewController:….