Use BOOL as property of button - objective-c

I have a boolean that I would like to set as a property of a button:
int tag = (int)[sender tag];
NSString* keyPath = [NSString stringWithFormat:#"addButton%d.hidden", tag];
[self setValue:YES forKey:keyPath];
I can't do this directly as the addButton's number changes according to the sender's tag.
I've already tried with:
setValue:[NSNumber numberWithBool:YES]
but doesn't work.
Where am I wrong?

This whole problem is based on bad architecture. Having properties addButton1, addButton2, addButton3 etc. is hard to work with in the first place. Every time you see yourself adding numbers at the end of your properties, use an array instead.
NSArray *addButtons = #[self.addButton1, self.addButton2, self.addButton3];
and then simply
[addButtons[sender.tag - 1] setHidden:YES];
Using KVC is good only for specific situations. If you are a beginner, try to not use it. It's a bad habit to overuse it. Access properties directly, not using string names.

It's probably setValue:forKeyPath:, the value must be an object
[self setValue:#(YES) forKeyPath:keyPath];

Related

How do apple's objects show properties in their description, and can I do the same?

I have an object with two properties - type and name - which I want to show in its description. The out-of-the-box description looks like this:
<SGBMessage: 0x7663bb0>
If I override description, like so:
return [NSString stringWithFormat:#"<%#: %x type:%# name%#>",
[self class], (int)self, self.type, self.name];
Then I can get a nice description like this:
<SGBMessage: 0x7663bb0 type:loadScreen name:mainScreen>
So far, so good. But Apple's objects have dynamic descriptions; if I look at a view's description I get this:
<UIView: 0x767bcb0; frame = (0 0; 0 0); layer = <CALayer: 0x767bd50>>
But if I set hidden to true, I get this:
<UIView: 0x767bcb0; frame = (0 0; 0 0); hidden = YES;
layer = <CALayer: 0x767bd50>>
Now, I don't believe for a second that they've got a massive set of if statements in the description methods of all of their objects; it seems much more likely that there's some method in some category somewhere on NSObject that can be overridden to specify which properties show up in the description. Does anyone know what's really going on, and if so, is it something I can take advantage of?
Mine tend to follow this pattern:
- (NSString *) description {
NSMutableDictionary *descriptionDict = [[NSMutableDictionary alloc]init];
if (account) [descriptionDict setObject:account forKey:#"account"];
if (date) [descriptionDict setObject:date forKey:#"date"];
if (contentString) [descriptionDict setObject:contentString forKey:#"contentString"];
return [descriptionDict description];
}
You could use a similar approach to build an NSMutableArray, and then iterate through the array, adding what's in there to the string.
For more complex apps, if you have custom classes that inherit from other classes, you can also make a separate method that returns descriptionDict, and then in the subclass call NSMutableDictionary *descriptionDict = [super descriptionDict] and continue adding / removing elements to it.
NOTE: The reason I use if statements on each line is that if one object happens to be nil, an exception is thrown. This will cause "no objective c description available" to print when you try to po your object.
But to answer your question, there's no secret way to make certain properties appear in the description. You just have to build a string yourself, by whatever means you decide is appropriate.

Correct way of setting a BOOL property

I have a BOOL property that I want to set in my class initializer.
#property (assign, nonatomic) BOOL isEditMode;
- (id)init
{
. . .
[self setValue:NO forKey:isEditMode];
return self;
}
The compiler gives me an "Incompatible integer to pointer conversion" warning. What am i doing wrong here?
The Key-Value Coding method setValue:forKey: only accepts objects as arguments. To set a BOOL, you need to wrap the number in a value object with [NSNumber numberWithBool:NO]. But there's little reason to do that. Key-Value Coding is a roundabout way to accomplish this. Either do self.isEditMode = NO or just isEditMode = NO. The latter is preferable in an init method (because setters can run arbitrary code that might not be desirable before an object is fully set up).
But to elaborate on the first point: The reason Key-Value Coding works this way is because the type system can't represent an argument that's sometimes an object and at other times a primitive value. So KVC always deals with objects and just autoboxes primitive values as necessary. Similarly, if you do [yourObject valueForKey:#"isEditMode"], you'll get back an NSNumber object wrapping the real value.
The correct syntax to set a property is just
self.isEditMode = NO;
If you want to use -setValue:forKey: you'd have to write it as
[self setValue:[NSNumber numberWithBOOL:NO] forKey:#"isEditMode"];
However, there's absolutely no reason to do this in your situation.
That said, since you're in an init method, I would strongly recommend avoiding any property access whatsoever and instead using the ivar directly, as in
isEditMode = NO;
This avoids the possibility of an overridden setter being called (either in this class or a subclass) that makes the assumption that the object has already completed initialization. For this same reason you also want to avoid property access inside of -dealloc.
You can just assign the value directly:
isEditMode = NO;
I think you mean:
self.isEditMode = NO;
If your code does indeed compile (I'm pretty new to Objective-C so I don't know) setValue probably takes a pointer to a string (#"isEditMode", e.g.) and not some other type (isEditMode, e.g.).

Dynamic button selection

Suppose I have two buttons that are ivar outlets. One is called "Blue" and the other "Red." Now, I have an NSString with the value of "Red." I want to set the button identified by the NSString to a selected state, without using if.
I do not want to do this:
NSString *button=#"Red";
if ([button isEqualtoString:#"Blue")
self.blue.selected=YES; //self.blue and self.red are UIButtons
else
self.red.selected=YES;
This is fine if you have two buttons, but I have quite a lot more than that, and it would be quite inelegant and cumbersome to do this for like 30 buttons.
I'd much rather find a way to directly link the name of a UIButton ivar to the value of an NSString.
Put the string lower case and
you can use KVC for that :
[self setValue:[NSNumber numberWithBool:YES]
forKeyPath:[NSString stringWithFormat:#"%#.selected", button]];
You can put the buttons in a dictionary :
[NSDictionaryName addObject: UIButtonName forKey: #"blue"];
or something like that :
[NSDictionaryName objectForKey:#"blue"].selected = YES;

Objective-C dictionary inserting a BOOL

OK, I'm a little confused.
It's probably just a triviality.
I've got a function which looks something like this:
- (void)getNumbersForNews:(BOOL)news andMails:(BOOL)mails {
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setValue:news forKey:#"getNews"];
[parameters setValue:mails forKey:#"getMails"];...}
It doesn't matter whether I use setValue:forKey: or setObject:ForKey:, I'm always getting a warning:
"Passing argument 1 of set... makes pointer from integer without a cast"...
How on earth do I insert a bool into a dictionary?
Values in an NSDictionary must be objects. To solve this problem, wrap the booleans in NSNumber objects:
[parameters setValue:[NSNumber numberWithBool:news] forKey:#"news"];
[parameters setValue:[NSNumber numberWithBool:mails] forKey:#"mails"];
Objective-C containers can store only Objective-C objects so you need to wrap you BOOL in some object. You can create a NSNumber object with [NSNumber numberWithBool] and store the result.
Later you can get your boolean value back using NSNumber's -boolValue.
Modern code for reference:
parameters[#"getNews"] = #(news);
A BOOL is not an object - it's a synonym for an int and has 0 or 1 as its values. As a result, it's not going to be put in an object-containing structure.
You can use NSNumber to create an object wrapper for any of the integer types; there's a constructor [NSNumber numberWithBool:] that you can invoke to get an object, and then use that. Similarly, you can use that to get the object back again: [obj boolValue].
You can insert #"YES" or #"NO" string objects and Cocoa will cast it to bool once you read them back.
Otherwise I'd suggest creating dictionary using factory method like dictionaryWithObjectsAndKeys:.
Seeing #Steve Harrison's answer I do have one comment. For some reason this doesn't work with passing object properties like for e.g.
[parameters setValue:[NSNumber numberWithBool:myObject.hasNews] forKey:#"news"];
This sets the news key to null in the parameter NSDictionary (for some reason can't really understand why)
My only solution was to use #Eimantas's way as follows:
[parameters setValue:[NSNumber numberWithBool:myObject.hasNews ? #"YES" : #"NO"] forKey:#"news"];
This worked flawlessly. Don't ask me why passing the BOOL directly doesn't work but at least I found a solution. Any ideas?

How to use #encode() to get #"NSArray" in Objective-C

I'm using the runtime functions to get the type of a property (thanks to eJames for helping me to figure out this way).
The attribute string of the property looks like this:
T#"NSArray",&,Vstuff
I need to check if the property type is an array, at the moment I'm doing it like this:
- (BOOL)valueForKeyIsArray:(NSString *)key fromTagret:(id)target
{
NSString *lowerCaseKey = [self convertToKVCKey:key];
objc_property_t property = class_getProperty([target class], [lowerCaseKey UTF8String]);
NSString *propertyAttrs = [NSString stringWithUTF8String:property_getAttributes(property)];
NSString *encodedType = #"#\"NSArray\"";
NSRange range = [propertyAttrs rangeOfString:encodedType options:NSLiteralSearch];
return range.location != NSNotFound;
}
But since Apple can change the type definition string at any time, I would like to generate this #"NSArray" type string. I tried it with #encode(), but it did not work:
NSString *encodedType = [NSString stringWithUTF8String:#encode(NSArray *)];
So how can I generate this type string? Or is there a better way to check if this property attributes contain the array type?
There is no way to check this. In Objective-C source code the variables being typed as NSArray * is only there for the compiler to issue warnings. It has no meaning, and does not exist at runtime. If you mis-typed an NSArray as an NSString, you would get lots of warnings when compiling, but your code would behave exactly the same when run. At runtime all that is known is that the ivar/property is "an object".
Another way to think of it, is that once Objective-C is compiled, all object references are id references.
Just accept that if the runtime changes, your code will break, and move on. However, I think you might be miscategorizing ivars of type NSMutableArray *, CFArrayRef, or CFMutableArrayRef. You also seem to be assuming all keys correspond directly to a declared property.
The cleanest solution might be to assert that the sample object being used for the test (the target) must have a non-nil value for that key, and just grab the value and test that [[target valueForKey:key] isKindOfClass:[NSArray class]].