UITabBarController Subview Gotcha

By far the most view blog article that I have, and subsequent Github project, is my Universal iOS App Template.  Unfortunately, it has been a tad bit neglected. There were some memory leaks, still supported 3.2 and not iOS5 compatible.  Now that I am working from home I have dedicated part of my day to personal projects and the first order of business is to get the app template up-to-date.  Most of the work was just cleanup and minor tweaks.  One of the branches however required a little bit more work. The template that comes with TabBarController support. A few months ago I added in the feature of having a “Tweetie” style indicator slider.  In 3.2 - 4.x iOS everything worked fine.  In iOS5 the indicator was not initially centered in relation to the first TabBarItem, but the entire TabBar.  When you selected another TabBarItem the indicator would move, but not in the correct position either.

The number of subviews are different between the iOS4 and iOS5.

--- iOS5
2011-11-17 09:14:54.290 UniversalExample[8989:f803] view frame: {{0, 0}, {768, 1024}}
2011-11-17 09:14:54.293 UniversalExample[8989:f803] view frame: {{181, 1}, {76, 48}}
2011-11-17 09:14:54.294 UniversalExample[8989:f803] view frame: {{291, 1}, {76, 48}}
2011-11-17 09:14:54.296 UniversalExample[8989:f803] view frame: {{401, 1}, {76, 48}}
2011-11-17 09:14:54.297 UniversalExample[8989:f803] view frame: {{511, 1}, {76, 48}}

--- iOS4
2011-11-17 09:16:06.751 UniversalExample[9033:b303] view frame: {{181, 1}, {76, 48}}
2011-11-17 09:16:06.754 UniversalExample[9033:b303] view frame: {{291, 1}, {76, 48}}
2011-11-17 09:16:06.754 UniversalExample[9033:b303] view frame: {{401, 1}, {76, 48}}
2011-11-17 09:16:06.755 UniversalExample[9033:b303] view frame: {{511, 1}, {76, 48}}

I now confirmed that there indeed was a difference in how the center position was being calculated. In iOS5 the first subview is the entire frame of the view.  However, I didn’t know which view(sub or super) that it might be referring to.

The best uiview debugging tool for situations like this is the recursiveDescription method.  If you haven’t heard or used it before I suggest/encourage you read Oliver Drobnik’s blog on it. In summary it is a private method to iterate over a views hierarchy.

After calling this method on the subviews in both iOS versions I saw the culprit. The first subview of a UITabBarController is an UITabBarBackgroundView, which obviously is going to extended the entire length of the tab bar.

iOS4


iOS5


Why did Apple add this subview you might ask? They added it because of welcome changes to UIKit.  Since iOS 2.0 developers have needed the ability to customize many of the UI elements, but unfortunately there wasn’t a straight forward way to accomplish this. The categories and hacks that people came up with are pretty cool, but not maintainable. With iOS5, and updates to UIKit, you can customize background images, tint color, selected images, etc. to your heart’s desire.


In order to fix the iOS5 bug in my template I just had to add a quick runtime check on the OS version and increment the index by 1.

All is right with the world.

Force UINavigationBar Back Bar Item Display

The latest project that i have been working on has the CRAZIEST workflow that I have ever experienced.  The app isn't in the app store, but is used as a sales tool for the type of services that their company can offer.  Surprisingly, at least to myself, I was able to come up with my own navigation controls to manage the workflow.  The workflow consisted of various tabbarcontrollers, uinavigationcontrollers, fullscreen views, some with navigation bar, some without.  What ended up being the trickest parts to deal with was handling the transitions between full screen views and views with the navigation bar along with the back button.  My first attempt in the transition worked except for the fact that the back bar item was BEHIND the uinavigationbar titleView.

The problem was that I was setting the back baritem to visible in viewWillAppear:animated: and then calling setNeedsDisplay on the navigationbar.  That works, but the "refresh" of the view and animation would stop once the method ended.  The fix ended up being setting the back baritem to hidden and refreshing the navigationbar in viewWillAppear:animated: