I have an app that displays the nodes of a connected graph. When the user touches a node I highlight the paths to the adjacent nodes by showing a clearColor "connections" view behind the nodes that is sized to contain the nodes of interest. This view contains child views for each path and each of these path views has a drawRect method that draws a single line that appears behind the source node and each adjacent node. All well and good and this approach opens the potential for doing interesting things with the paths in the future.
However, I recently added code to detect an external display with the idea that the main display would be a zoomed in view inside a scroll view and the external display would show the global view of all nodes. It worked fine until I added the connection view to the external display to show what was selected by touch on the main display. My path views (and their parent and grand parent views) are identical as far as I can tell, never have their drawRect methods called, as do their main view cousins, after setNeedsDisplay has been called. They are both initialized in the same place and in the same way.
I've tried dozens (hundreds?) of things to isolate the problem, but haven't figured out the problem. If I add a generic UIView to the connections view it appears in both places, but my path view, which extends UIView by adding a drawRect, only gets called on my main display and never on my external display.
I've run out of ideas. Does anyone have any ideas on what might be the cause of the problem?
I was pulling hair trying to figure this one out. Comparing the state of the two views did not show any difference. A few days ago I finally decided to write some a python routine to run in LLDB that would dump the state of the objects and then did a diff on the two views. It turns out that they were attempting to show the same subviews!
In order to minimize object creation I was stuffing the path subviews into an array to avoid creating more than I ever needed and hiding them when not in use. But, I had stupidly declared this array outside of the #interface block and had inadvertently made it into a global and so both views were using the last set of subviews defined. Of course, a view cannot be a child of more than one parent view. Moving the declaration into the right place fixed the problem.
Dumb error! But perhaps this will jog something for someone else with a similar problem.
Related
I would like to implement something like below:
But, I would like to have my first version app without the map, later on, I will add the map. So, the eventual goal is to show both map and the other things showing on above screen.
My question is, how to make it happen? It sounds like I should have the map and the rest UI in two files (xib or storyboard) respectively, then later on, combine them together. But I am not sure what is the right way to implement these things in iOS?
Can I implement a view component which only contains map, then, another view component only contains the rest, then later on, combine them & one UIViewController associate with the combined view? If so, how to do it? If not, what is the correct way to achieve this?
I think you can achieve that by using container views; add 2 container views to your view controller in your Storyboard, the upper container view will contain the map and its stuff, and the bottom one will contain anything else.
To hide the first container view in your first version of the app you'll only need to set the height constraint of the upper container view to 0 after setting the other constraints properly.
If you wanted to use a view controller from another Storyboard file, you can check my answer here.
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 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.
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.