Tab bar Controller raising NSInternalInconsistencyException - iphone

In my SDK 3.0 core data based app, I have a tab bar controller managing 4 tabs. From time to time, apparently randomly, when I launch the app, it crashes with the following message:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Directly modifying a tab bar managed by a tab bar controller is not allowed.'
However, I am not modifying in my code any of the tabs except for the following. In practice, all of the navigation controllers or controllers in the tabs have been setup using IB, and in my code I have declared two of them as outlets, since I need to access them in my app delegate applicationDidFinishLaunching() method to setup their core data managedObjectContext as follows:
[self managedObjectContext];
[self managedObjectModel];
[self persistentStoreCoordinator];
[rootViewController retain];
rootViewController.managedObjectContext = self.managedObjectContext;
Is this not correct? If so, why?
The only reference to the problem I have seen on the web is here:
http://discussions.apple.com/thread.jspa?messageID=9716886
However, the problem still persists even after deleting and recreating the tab bar controller from scratch in IB.
Any clue?
Thanks in advance.

I've had this problem too. Do you have an outlet to the UITabBar itself (not the UITabBarController) in the nib? When I removed that, I stopped having problems.
Sorry this isn't a 100% reliable explanation, but this workaround cleared the problem up for me.

I've gotten this exception a few times, especially when changing things with localizations. Cleaning the targets and then rebuilding seems to work around the issue.

I quickly wrote the following class and showing/hiding tab views from UITabBarController worked like magic:
TabBarDesigner.h
#import <Foundation/Foundation.h>
#interface TabBarDesigner : NSObject
{
}
+(void) setTabBarController:(UITabBarController *)tabBarController
items:(NSArray *)tabBarItems
viewControllers:(NSArray *)viewControllers;
+(void) removeItemsInRange:(NSRange) range;
#end
TabBarDesigner.m
#import "TabBarDesigner.h"
static NSArray *_tabBarItems = NULL;
static NSArray *_viewControllers = NULL;
static UITabBarController *_tabBarController = NULL;
#implementation TabBarDesigner
+(void) setTabBarController:(UITabBarController *)tabBarController
items:(NSArray *)tabBarItems
viewControllers:(NSArray *)viewControllers
{
if (tabBarItems && viewControllers && tabBarController)
{
if ([tabBarItems count] == [viewControllers count])
{
[_tabBarItems release];
[_viewControllers release];
_tabBarItems = [tabBarItems copy];
_viewControllers = [viewControllers copy];
_tabBarController = tabBarController;
}
}
}
+(void) removeItemsInRange:(NSRange) range
{
if (_tabBarController)
{
if ( range.location < ([_tabBarItems count] - 1) )
{
if ( (range.length + range.location) < [_tabBarItems count] )
{
NSMutableArray *tabBarItems = [_tabBarItems mutableCopy];
[tabBarItems removeObjectsInRange:range];
NSMutableArray *viewControllers = [_viewControllers mutableCopy];
[viewControllers removeObjectsInRange:range];
[_tabBarController setViewControllers:viewControllers];
NSUInteger i;
for (i = 0; i< [viewControllers count]; i++)
{
UIViewController *vC = [viewControllers objectAtIndex:i];
vC.tabBarItem.image = [[tabBarItems objectAtIndex:i] image];
vC.tabBarItem.title = [[tabBarItems objectAtIndex:i] title];
vC.tabBarItem.tag = [[tabBarItems objectAtIndex:i] tag];
}
[tabBarItems release];
[viewControllers release];
}
}
}
}
#end
A sample of how to use this class:
In your MyAppDelegate.m
#import "TabBarDesigner.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[TabBarDesigner setTabBarController:_tabBarController
items:[_tabBarController.tabBar items]
viewControllers:[_tabBarController viewControllers]];
// remove the first 3 tabs
[TabBarDesigner removeItemsInRange:NSMakeRange(0,3)];
// show all tabs
[TabBarDesigner removeItemsInRange:NSMakeRange(0,0)];
// continue with your code
}
Cheers!

Related

Unloading viewControllers from UIPageViewController

I got a UIPageViewController where I add other viewControllers. Those ViewControllers are in the array viewControllersArray = [[NSMutableArray alloc] init]; I add viewControllers on this array like this:
[viewControllersArray addObject: infoViewController];
After the viewControllers are added on my array:
NSArray *initialViewControllers = [[NSArray alloc] initWithObjects:[viewControllersArray objectAtIndex:0], nil];
[self setViewControllers:initialViewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:^(BOOL finished){
NSLog(#"call back success");}];
This above code is all done in the viewDidLoad from my UIPageViewController.
For loading this array I got those functions form <UIPageViewControllerDataSource>
which I use like this:
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
if ([viewControllersArray containsObject:viewController]) {
NSInteger index = [viewControllersArray indexOfObject:viewController];
if (index < [viewControllersArray count] && index > 0) {
return [viewControllersArray objectAtIndex:(index - 1)];
}
}
return nil;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
if ([viewControllersArray containsObject:viewController]) {
NSInteger index = [viewControllersArray indexOfObject:viewController];
if (index < [viewControllersArray count] - 1) {
return [viewControllersArray objectAtIndex:(index + 1)];
}
}
return nil;
}
Now what the problem is, is that when you swipe, the next viewcontrollers gets load, etc. But they don't get unloaded. So I'm searching for a way to unload the viewcontrollers that you already passed to save memory, and when you swipe back they will get reloaded.
your problem is that you hold on to your view controllers in the viewControllersArray. The array holds a strong reference to each object that you add to it. The easiest way to save memory is to just create the requested view controller on the fly in both the pageViewController:viewControllerBeforeViewController: and the pageViewController:viewControllerAfterViewController: data source methods. That way, only the page view controller will have a strong reference to the view controller and will release (dealloc) it once it is moved off-screen.
If it's too expensive to create them on-the-fly, you might consider just keeping 3 view controllers in the array: the one currently displayed and the one to the immediate left and the immediate right.
Example:
- (UIViewController *)pageViewController:(UIPageViewController *)pvc
viewControllerBeforeViewController:(MyGreatViewController *)vc
{
NSUInteger index = vc.position - 1; //custom property on your VC
if (index > 0) {
return [[MyGreatViewController alloc] initWithPosition:index];
}
return nil;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pvc
viewControllerBeforeViewController:(MyGreatViewController *)vc
{
NSUInteger index = vc.position + 1; //custom property on your VC
if (index <= MAX_POSITION) {
return [[MyGreatViewController alloc] initWithPosition:index];
}
return nil;
}
Enjoy!
You should not unload View Controller. If memory is low, controllers unload their views. In code, use -(void)viewDidUnload and -(void)didReceiveMemoryWarning (iOS 6) to free your resources. When UIPageViewController will show some page, controller recreate unloaded view and call -(void)viewDidLoad (if you customize your controllers manually).

How to access the views of the MoreViewController and change their titles?

on language switch within my App, I need to access the views of the MoreViewController in a TabBar and change their titles.
Could anyone pls tell me how to do that?
Your help is much appreciated.
Cols
Here are some snippets that might work for you. Note that all of the below is subject to break on each and every new iOS release at it is not meant to be done.
Customizing the More view title as itself as well as the title while it is being edited by the user.
- (void)customizeTitleViewWithNavigationItem:(UINavigationItem *)navigationItem
{
VASSERT(navigationItem != nil, #"invalid navigationItem supplied", navigationItem);
UILabel *titleView = [[UILabel alloc] initWithFrame:CGRectZero];
titleView.backgroundColor = [UIColor clearColor];
titleView.font = [UIFont boldSystemFontOfSize:20.0f];
titleView.text = navigationItem.title;
[titleView sizeToFit];
navigationItem.titleView = titleView;
[titleView release];
}
This needs to be implemented within the UINavigationController's delegate;
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
if (navigationController == tabBarController_.moreNavigationController)
{
if ([viewController isKindOfClass:NSClassFromString(#"UIMoreListController")])
{
[self customizeTitleViewWithNavigationItem:viewController.navigationItem];
}
else
{
NSLog(#"viewController (%#) does not seem to be a UIMoreListController", viewController);
}
}
else
{
NSLog(#"navigationController (%#) does not seem to be the moreNavigationController", navigationController);
}
}
This needs to be implemented within the UITabBarController's delegate;
- (void)tabBarController:(UITabBarController *)controller willBeginCustomizingViewControllers:(NSArray *)viewControllers
{
//get the second view of the upcoming tabbar-controller
UIView *editView = [controller.view.subviews objectAtIndex:1];
//did we get what we expected, which is a UITabBarCustomizeView?
if (editView != nil && [editView isKindOfClass:NSClassFromString(#"UITabBarCustomizeView")])
{ //yes->get the navigation-view
UIView *navigationView = [editView.subviews objectAtIndex:0];
//is that a navigationBar?
if (navigationView != nil && [navigationView isKindOfClass:[UINavigationBar class]])
{ //yes->...
UINavigationBar *navigationBar = (UINavigationBar *)navigationView;
[self customizeTitleViewWithNavigationItem:navigationBar.topItem];
}
else
{
NSLog(#"the navigationView (%#) does not seem to be a navigationBar", navigationView);
}
}
else
{
NSLog(#"the editView (%#) does not seem to be a UITabBarCustomizeView", editView);
}
}
I was able to change the title of one of the tabBar items for my 6th view controller (the last one in the more list) with the following code:
NSArray *vcs = [(UITabBarController *)self.window.rootViewController viewControllers];
[[vcs.lastObject tabBarItem] setTitle:#"New Title"];
Is this what you want to do?
After Edit: To change these titles after they are set up the first time, you need to re-set the viewControllers property of the tabBar controller. In this code example, I connected a button in my 6th view controller to an action method that changes the titles of 3 of my controllers, 2 in the more list and one in the regular list.
-(IBAction)changeNames:(id)sender {
UITabBarController *tbc = (UITabBarController *)[[UIApplication sharedApplication] delegate].window.rootViewController;
NSArray *vcs = tbc.viewControllers;
[[vcs.lastObject tabBarItem] setTitle:#"New Title"];
[[[vcs objectAtIndex:4] tabBarItem] setTitle:#"New VC"];
[[[vcs objectAtIndex:3] tabBarItem] setTitle:#"New VC2"];
tbc.viewControllers = tbc.viewControllers;
}

Need Help with applicationDidBecomeActive

I have been trying for days to get this code to work, but I have no idea what I am doing wrong. Everytime the app wakes up from sleep, or the user closes the app and opens it again (without closing the app from multitasking), I want a label value to change.
In my applicationDidBecomeActive, I am running a counter, which I want to display on whatever viewcontroller is open at that moment.
Code:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
In my viewcontroller W1G1, I have the following code:
Code:
- (void) setlabel {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
I have imported W1G1 in my appdelegate, but the code does not run :( Please help!
Thanks
In the AppDelegate.m file, where you have
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
the variable counter being incremented is confined to the AppDelegate. In other words, your view controller doesn't know that it has been incremented.
I would suggest that you use NSUserDefaults to store the value of counter so that you can easily pass it between these view controllers. Either that, or you could allow for an input into the method setLabel, e.g.
- (void) setlabel:(int)counter {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
and then in the AppDelegate you'll want to do:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel:counter]; // <-- now you're using counter
[self.window addSubview:view1];
}
1) When you say 'the code does not run' do you mean that? That is, if you put NSLogs in applicationDidBecomeActive: and in setLabel does it show the code is run?
2) I would suspect the code is running. But your code won't "show the counter on whatever view controller is open at that moment". Your code creates a new view (view1), but that view won't be displayed. It is not added as a subview to anything. Your code will also leak. You create a W1G1 object, but it is never released and you throw away any reference you have to it.
To achieve what you want, you could add a subview to the application's window. Depending how your app delegate is set up, something like the following should do the trick:
counter++;
W1G1 *viewController1 = [[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil];
[viewController1 setlabel: counter];
[[self window] addSubview: [viewController1 view]]
// you'll want to save a reference to the viewController somehow so you can release it at a later date
Then in W1G1
- (void) setlabel: (int) counter;
{
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
There are, of course, lots of other approaches you could take towards this problem. And you'll need some strategy for removing the W1G1 view that you are adding at some stage, otherwise you'll just get more and more views added.
Update: You ask (in comments) how to keep track of your viewController throughout lifetime of the app... One approach is to keep track of it in your appDelegate. In the header have something like:
#class W1G1;
#interface MyAppDelegate : : NSObject <UIApplicationDelegate>
{
// other decelerations
int counter;
W1G1 * _myW1G1
}
#property (nonatomic, retain) W1G1* theW1G1
In the .m file include
#synthesize theW1G1 = _myW1G1;
Probably in application:didFinishLaunchingWithOptions: create the viewController, set the property to refer to it, and add its view to the view hierarchy.
W1G1* theViewController = [[W1G! alloc] initWithNibName: #"W1G1" bundle: nil];
[[self window] addSubview: [theViewController view]];
[self setTheW1G1: theViewController];
[theViewController release];
Then when you want to access the viewController again from with the app delegate use [self theW1G1], e.g.
[[self W1G1] setlabel: counter];

TableView obstructed by Titlebar

I'm building my first basic tabbed, application with one of the views as a navigation controller that will display a view controller.
I'm running into an issue at the point the user selects a category from the first tableview as shown in the screenshot: http://www.cl.ly/7YOF
When another instance of the tableviewcontroller is loaded and pushed onto the stack of the navigationcontroller, the table is obstructed by the title bar:
http://www.cl.ly/7ZRz
The table view select logic is below:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
KHCategory *selectedItem = [categoryArray objectAtIndex:indexPath.row];
if (selectedItem.categories.count > 0) {
KHCategoryTableViewController *nextCategoryController = [[KHCategoryTableViewController alloc] init];
nextCategoryController.categoryArray = [[NSArray alloc] initWithArray:selectedItem.categories];
nextCategoryController.title = selectedItem.labelValue;
[self.navigationController pushViewController:nextCategoryController animated:YES];
[nextCategoryController release];
} else {
NSLog(#"show detail view");
}
}
EDIT:
I should be clear that an instance of KHCategoryTableViewController is the root of my NavigationController and the NavController is wired up to the first tab of a TabController.
Two interesting things: it measures 20 pixels down (size of status bar) and your line "nextCategoryController.title = ..." doesn't seem to do anything. So...
1) I assume you haven't used setStatusBarHidden?
2) Looks like navController stuff isn't working. Can you give the code from the appDelegate that creates the tabBar and NavController?
3) Add this code, and try calling [self dumpWindow: #"VDL"] from your Subcategory ViewDidLoad method. I find it invaluable whenever checking whether my view structure is correct.
- (void) dumpWindowFrom:(NSString *) fromText {
[self dumpViews: nil from:fromText];
}
void dumpViewsRecursive(UIView* view, NSString *text, NSString *indent) {
Class cl = [view class];
NSString *classDescription = [cl description];
if ([text compare:#""] == NSOrderedSame)
NSLog(#"%d: %# %# %#", (int)view, classDescription, NSStringFromCGRect(view.frame), view.hidden ? #"Inv" : #"Vis");
else
NSLog(#"%d: %# %# %# %#", (int)view, text, classDescription, NSStringFromCGRect(view.frame), view.hidden ? #"Inv" : #"Vis");
for (NSUInteger i = 0; i < [view.subviews count]; i++)
{
UIView *subView = [view.subviews objectAtIndex:i];
NSString *newIndent = [[NSString alloc] initWithFormat:#" %#", indent];
NSString *msg = [[NSString alloc] initWithFormat:#"%#%d:", newIndent, i];
dumpViewsRecursive (subView, msg, newIndent);
[msg release];
[newIndent release];
}
}
- (void) dumpViews: (UIView *) view {
dumpViewsRecursive (( (!view) ? [[UIApplication sharedApplication] keyWindow] : view), #"" ,#"");
}
- (void) dumpViews: (UIView *) view from:(NSString *) fromText{
dumpViewsRecursive ((!view) ? [[UIApplication sharedApplication] keyWindow] : view, fromText, #"");
}
4) You could always just cheat and add:
CGRect frame = [nextCategoryController.view frame];
frame.origin.y = frame.origin.y+20.0;
[nextCategoryController.view setFrame:frame];
Check the autoResizingMask of your KHCategoryTableViewController's view.
UINavigationController overview at iPhone Dev Center says:
Note: Because the amount of space
available for the custom view can vary
(depending on the size of the other
navigation views), your custom view’s
autoresizingMask property should be
set to have a flexible width and
height. Before displaying your view,
the navigation controller
automatically positions and sizes it
to fit the available space.
This issue became resolved when I built against iOS 4.3 and not iOS 5.

Initializing view controller referenced from application launch

My data is stored in a member variable (NSArray) of a view controller. The problem I'm running into is that my data is loaded from a database on application launch, but the NSArray isn't initialized until later, so the addObject calls silently fail.
I've tried putting breakpoints on the init, initWithNibName, viewWillAppear, and viewDidLoad methods of my view controller (SafeTableViewController), but none of them catch before the addObject call. I assume that the actual view controller is initialized, because when I watch it in the debugger it has a nonzero address, but the NSArray has the address 0x0 when addObject is called.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
databaseName = #"DubbleDatabase.sql";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
[self checkAndCreateDatabase];
[self readSafeItemsFromDatabase ];
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
return YES;
}
- (void) readSafeItemsFromDatabase {
// some code skipped here, but basically: open sqlite3 database, iterate through rows
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// read database, get data fields out
SafeItem *safeItem = [[SafeItem alloc] initWithName:aName price:aPrice category:aCategory];
[safeTableViewController addItemToSafe: safeItem]; // PROBLEM HERE
[safeItem release];
}
}
sqlite3_close(database);
}
In SafeTableViewController.m:
- (void) addItemToSafe : (SafeItem*) newSafeItem {
[self.safeItems addObject: newSafeItem];
}
// I put a breakpoint on this, but it does not hit. i.e. safeItems is not initialized when addObject is called on it.
-(id) init {
if(self = [super initWithNibName:#"SafeTableViewController" bundle:nil]){
self.safeItems = [[NSMutableArray alloc] init];
}
return self;
}
EDIT: Thought of a way to fix this problem. Still curious though: when is init and/or initWithNibName called? Here's the proposed solution:
- (void) addItemToSafe : (SafeItem*) newSafeItem {
if(self.safeItems == nil){
self.safeItems = [[NSMutableArray alloc] init];
}
[self.safeItems addObject: newSafeItem];
}
How do you setup your instance of SafeTableViewController? By code? By nib?
-(id) init is not the designated initializer.
You probably want to use
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle {
if(self = [super initWithNibName:nibName bundle:nibBundle]){
self.safeItems = [[NSMutableArray alloc] init];
}
return self;
}
or initialize elsewhere, i.e. in viewDidLoad.
The problem is that you shouldn't be storing your data in a view controller. Create a model object (SafeItemManager for instance) to hold your data and point the view controller at that.