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.
Related
In my app, I have a very custom UITableView. The cells are all statically defined in Interface Builder, but based on the data structure the table morphs in many various ways. For example, if some data doesn't exist, some cells (or entire sections) are not displayed, custom separator lines are added to account for missing cells, extra views are loaded into the cells, VoiceOver labels change, etc. Because all the cells are static, I set up the table layout in viewDidLoad because I always have the data available at that time. I have always presented this view controller modally, which has worked great. If the user wants to display different data in this table they have to dismiss the view controller and pick a different item to present it again, and it gets rendered appropriately in all cases.
But now I am converting this into a split view controller for iPad, so this UITableViewController never disappears off screen, but I need to set up the table again when the user taps an item. The problem is, because the table is never deallocated, its previous layout still exists when I load more data into it. It would be a lot of work (and an excellent opportunity for many difficult to reproduce bugs to pop up) to test all possible scenarios and try to reset it back to its "pre viewDidLoad state" or undo those previous layout changes if not relevant anymore, if not impossible because I don't have references to the many different custom separator lines generated.
My question is, is it possible to completely reset the table view controller every time a row is selected in the master view controller, therefore allowing it to properly set up the layout because it is not stuck with the previous layout?
I essentially need some way to completely wipe it clean as if it never did any setup, then instantiate it again to cause viewDidLoad get called (or I can move that code to its own method or viewWillAppear). I'm basically looking for a way to reset the tableView back to how it is defined in Interface Builder.
I believe this would result in a flash because the table would completely disappear then reappear in a different format, but that would be acceptable. If that can be animated that'd be nice. If this is really not recommended at all, how do you suggest I proceed to ensure the layout is always appropriate for the data it is presenting?
I was over-thinking this. There's really no need to completely throw away the table and generate a new one. It turned out to be simpler than I had thought to reset the table back to its default state. Just had to be sure to catch every possible thing that could change, including VoiceOver labels, and reset to nil or the default value. Then it can run through the reset code then the layout code every time the data changes and render an appropriate layout. The most difficult part was to remove the custom separator lines, which I solved by adding each one to an array when it's created, then index through it and remove each one from its superview then remove the Autolayout constraints associated with it. One can wrap all of this into a UIView animation block to get a nice fading effect. It's working quite well.
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).
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'm using a table view with a very large header view (not section header) in order to take advantage of the various advantages of table views (performance on long lists, pull to refresh, etc).
The header is ~700 points tall and contains various interactive items - a map, two buttons & a horizontal collection view.
Currently, none of the touch events are being passed through to the controls. I have tried building my own UITableView subclass, overriding the touch events & sending them to the next responder, but this doesn't help.
Strangely, the table still scrolls fine, so I'm not sure why that's happening. I have confirmed that the events are firing through debugging.
As extra details - I'm using iOS 7, Xcode 5, autolayout is on & I'm using a lot of constraints. None of them are listed as conflicting though.
Any idea how I should resolve this?
Thanks
Okay, so I managed to get this working & to be honest it's difficult to tell exactly what it was I did which fixed the issue.
Basically, I had wrapped all my controls inside a main view in order to be able to apply a total height constraint to it. That view did have user interaction enabled, but it seems like it wasn't passing the events through for some reason.
In any case, I removed that wrapping view & everything is now working.
shrug
I actually embedded all my header.xib controls into a View. After that, I was able to click on the buttons.
I am trying to achieve the following effect:
A UICollectionView displays a grid of cells for a parent type of object, e. g. a photo album. When I tap one of these items, I would like to scroll that element to the top of the screen and open a Springboard like folder from it. Inside that folders area, another collection should be shown, consisting of the detail items, i. e. the individual photos of that album. Tapping in the remaining "parent" view closes the folder again. See this schema:
What I have done so far is a regular collection view for the albums. When I select one, it scrolls to selected item to the top and then uses JWFolders to open an empty folder at that place. Once that is shown, I trigger the surrounding UINavigationController to push my 2nd view controller with the detail items. That one is layed out so it appears to the user as if it were still the same view.
There are several problems with this approach, and I would like to know how to do this better:
JWFolders takes a screenshot and animated two halves of it up/downwards to achieve the opening effect. This is ok, but pretty slow on an iPad3, because it moves a lot of pixels and the iPad3's GPU is not quite up to the task.
The 2nd view needs to be pixel-perfect to match on top of the first one. This is likely to break accidentally.
I am limited as to what animations are possible for the view controller transition. The default UINavigationController's push from the right is not fitting. I override that to do a cross-dissolve, but still it is far from ideal.
I would like to get pointers as to how to approach this problem in a maintainable manner that does not require to much creative hacking against what the frameworks are designed to do. I might be missing something obvious here, so pointers to examples or general advice are appreciated.
Update:
I changed the approach a bit. Now I use a container view controller that has two embedded collection view controllers. One for the "Album" and one for the "Photos" part at the bottom. Using a UIImageView in the middle between the two I can get the triangle pointing upward done. This is also nice from a maintenance point of view, because it makes maintenance easier with the two collections being handled completely separately.
The app uses Auto Layout, so I can change the amount of space each of the two embedded views takes by modifying the constraints. This is way faster than the screenshot based approach with JWFolders and works nicely on an iPad3 as well.
This almost gets me where I want to be. The one thing that remains is to get the opening animation right. I would like to simultaneously scroll the Albums collection, so that the tapped item goes to the top and expand the photos collection with the triangle pointing at the Album cell.
Can I somehow "connect" the lower view to that cell via layout constraints, so that the scrollToItemAtIndexPath:atScrollPosition:animated: call drags the lower view open?
To get around it I would lose the library and cause iOS to move those display elements around without screenshots or other tricks. On the tap, cause the tapped icon to retain its normal appearance while you dim all the others. Find the contents of the collection view from the top to the end of the line where the tapped icon is. Create two new collection views - one which contains the top half, including your tapped icon and one containing the rest, below. Animate those views apart to make room for the folder view.
The folder view is another UICollectionView that appears in the gap created.
In the main view there are either one or three views presented depending on whether the drawer is open or closed. I would probably look at creating a view controller with a collection view, and using view controller containment to manage all three views. You have complete control over how those views are presented, so you could animate top and bottom views up and down simultaneously to reveal the folder view in place, as Springboard does.
When that's all working then you could generalize and start doing things like deciding to make the tapped icon part of the bottom collection with the folder appearing above if the icon was low on the screen.
(I hesitate to answer this because of the large number of upvotes yet no answers, so I may have missed something - but that is how I would begin trying to achieve the Springboard effect.)
To solve this problem in a relatively easy way, you could try to make the folder a simple UICollectionView subclass and then insert that cell when the albums cell is tapped.
In the collection views data source you would have to return different size etc. for the folder cell.
In the folder you would have to create the folders collection view, avoid making the folder cell the data source of the cell folder collection view tho.