I am new to Objective-C and could use some help with a problem that has left me stumped. I call it the problem of the disappearing variable. I will attempt to describe it below.
I’m attempting to create my own tab bar controller and view as I’ve concluded that UITabBarController and UITabBar don’t allow me to do what I need to do (I need to change the appearance of the tab bar quite drastically).
I’ve subclassed UIViewController and UIView to create my classes SYTabBarController and SYTabBar, respectively.
In SYTabBar, I’ve written a method to programmatically add UIButtons to my view:
- (UIButton*) addButtonWithFrame:(CGRect)frame action:(SEL)action target:(id)target imageToken:(NSString*)imageToken
{
UIButton *b = [[UIButton alloc]initWithFrame:frame];
[b addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
UIImage
*unselectedImage = [UIImage imageNamed:APPEND_STRING(@"TBI+Unselected+", @"imageToken")],
*selectedImage = [UIImage imageNamed:APPEND_STRING(@"TBI+Selected+", @"imageToken")],
*selectedBgImage = [UIImage imageNamed:@"TBI+SelectedBG"];
[b setImage:unselectedImage forState:UIControlStateNormal];
[b setImage:selectedImage forState:UIControlStateHighlighted];
[b setImage:selectedImage forState:UIControlStateSelected];
[b setBackgroundImage:selectedBgImage forState:UIControlStateHighlighted];
[b setBackgroundImage:selectedBgImage forState:UIControlStateSelected];
[b setShowsTouchWhenHighlighted:NO];
[b setAdjustsImageWhenHighlighted:NO];
[b setAdjustsImageWhenDisabled:NO];
[self addSubview:b];
return b;
}
I invoke this method in SYTabBarController to create five buttons:
UIButton *button1 = [self.tabBar addButtonWithFrame:CGRectMake(0, 0, 64, 45)
action:@selector(changeView:)
target:self
imageToken:@"Popular"];
UIButton *button2 = [self.tabBar addButtonWithFrame:CGRectMake(64, 0, 64, 45)
action:@selector(changeView:)
target:self
imageToken:@"Feed"];
UIButton *button3 = [self.tabBar addButtonWithFrame:CGRectMake(64*2, 0, 64, 45)
action:@selector(changeView:)
target:self
imageToken:@"Capture"];
UIButton *button4 = [self.tabBar addButtonWithFrame:CGRectMake(64*3, 0, 64, 45)
action:@selector(changeView:)
target:self
imageToken:@"Likes"];
UIButton *button5 = [self.tabBar addButtonWithFrame:CGRectMake(64*4, 0, 64, 45)
action:@selector(changeView:)
target:self
imageToken:@"Profile"];
Then, still in SYTabBarController, I try to take these five buttons and add them as keys in a dictionary which is stored as a property (see code below). The dictionary contains UIViewController instances keyed to the buttons. The idea is that when one of the five buttons is pressed, it will call the selector changeView:, which in turn will retrieve the respective UIViewController instance from the dictionary and display its view.
self.viewControllersKeyedToButtons = [NSDictionary dictionaryWithObjectsAndKeys:
rootViewController,
button1,
[[SYViewController_Feed alloc] init],
button2,
[[SYViewController_Capture alloc] init],
button3,
[[SYViewController_Likes alloc] init],
button4,
[[SYViewController_Profile alloc] init],
button5,
nil];
My code crashes right when I initialize the dictionary. I get this error message:
2012-12-27 02:54:28.498 Smuvy[17931:c07] -[UIButton copyWithZone:]: unrecognized selector sent to instance 0x717fc10
2012-12-27 02:54:28.500 Smuvy[17931:c07] * Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIButton copyWithZone:]: unrecognized selector sent to instance 0x717fc10’
* First throw call stack:
(0x174c012 0x1223e7e 0x17d74bd 0x173bbbc 0x173b94e 0x17ce714 0x1712b55 0x172d5a8 0x65d6 0x2c6e 0x1687b7 0x168da7 0x169fab 0x17b315 0x17c24b 0x16dcf8 0x3676df9 0x3676ad0 0x16c1bf5 0x16c1962 0x16f2bb6 0x16f1f44 0x16f1e1b 0x1697da 0x16b65c 0x2aad 0x29d5)
libc++abi.dylib: terminate called throwing an exception
(lldb)
Now here’s the mystery. When I track the variable button5 in my debugger, I notice that right before I try to instantiate the dictionary, it disappears! No wonder I get an error message.
As I am new to Objective-C (and to C) I don’t know much about memory management and I imagine I’ve done something wrong with my variables. I’ve run Instruments and interestingly enough, I see all UIButton instances alive and well. It seems, thus, that the button is there, it’s just the pointer that has somehow gone kaput.
Any help you can provide would be much appreciated.
When you create an NSDictionary, it makes copies of the keys that you provide, by calling
-copy, which calls-copyWithZone:. These methods are part of theNSCopyingprotocol.Unfortunately, UIButton does not conform to that protocol. It isn’t copyable. When NSDictionary tries to send the
-copymessage to it, an exception is raised, as you have discovered.Alternatives:
Use an
NSMapTableinstead.Wrap the keys in an NSValue, using
+[NSValue valueWithNonretainedObject:]or+[NSValue valueWithPointer:]. (If you’re using ARC, NSValue will not ensure that your object is retained, so you will need to make a strong reference to the object some other way.)Use some other method to determine a key that the view controller and the UIButton can share. (For instance, have them both refer to the same string.)
Don’t use a map at all. Perhaps keep your view controllers in an array, and use the
tagon the UIButton to store the index of the corresponding view controller in the array.