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

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.

Related

Use BOOL as property of button

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];

How to choose a method based on an element of an NSArray (Objective-C)

I'm writing a sort of calculator app. I have a UIPickerView (1 column) loading data from an NSArray of strings. The user will select one of these (it's selecting which type of calculator to use -- each uses a different method to calculate). The user inputs some things into some UITextFields and then presses a UIButton to do the calculations.
My NSArray is this:
calcNames = [NSArray alloc] initWithObjects:#"first", #"second", #"third", nil];
And my methods are called firstCalc(input1, input2, input3), secondCalc(input1, input2, input3), and so on. (The inputs are coming from the UITextFields.)
When I press the button, I would like to tell it to look at what the selection in the UIPickerView is and run the corresponding method without just typing an if-then statement for each one (it's very inconvenient to do this for reasons specific to my app, which are beyond the scope of this discussion).
So I have already defined a way to determine what the selected calc is:
selectedCalc = [[NSString alloc] initWithString:[calcNames objectAtIndex:row]]
where 'row' is the current selection in the UIPickerView.
Now I have a doCalculations method for when someone presses the UIButton:
-(IBAction)doCalculations:(id)sender {
// save the data input
double input1 = [input1Field.text doubleValue];
double input2 = [input2Field.text doubleValue];
double input3 = [input3Field.text doubleValue];
// do the calculations
int i;
for (i = 0; i < [calcNames count]; i++) {
if (selectedCalc == [calcNames objectAtIndex:i]) {
// do calculations here
double numResult = ??????
// if selectedCalc is "first", I want it to do firstCalc(input 1, input 2, input 3)
// if selectedCalc is "second", I want it to do secondCalc(input 1, input 2, input 3), and so on
// the rest is just for displaying the result
NSString* result = [NSString stringWithFormat:#"The answer is %f", numResult];
[resultLabel setText:result];
}
}
}
So basically, it runs a for loop until it finds which calculator is selected from the UIPickerView and when it finds it, runs the calculations and displays them.
I've been trying to understand if maybe function pointers or selectors (NSSelectorFromString?) are the right things to use here and how to use them, but I'm really struggling to understand where to go after a couple days of reading Apple's documentation, Stack Overflow questions, playing with sample code, and tinkering with my own code.
Sorry if the question is too lengthy, I thought it may be more helpful to others looking for assistance in the future to see the full idea. (At least I know sometimes I'm lost with these question pages.)
I would be very grateful for any assistance,
Ryan
You can dynamically invoke a method using a selector. You could for example have a secondary array to calcNames with selector called calcSelectors:
SEL calcSelectors[] = (SEL[3]){
#selector(first:arg:),
#selector(second:arg:),
#selector(third:arg:)};
Calling the right method would then be as simple as:
[self performSelector:calcSelectors[calcIndex] withObject:arg1 withObject:arg2];
If you need more then 2 arguments, then you also need to mess a bit with a NSInvocation instance to setup the call.
Example 1:
NSString *method=[calcNames objectAtIndex:0];//here play with objectatindex
SEL s=NSSelectorFromString(method);
[self performSelector:s];
which will call this method
-(void)first{
NSLog(#"first");
}
-----------------------------------------
Example 2:
NSString *totalMethodName;
totalMethodName=#"vijay";
totalMethodName=[totalMethodName stringByAppendingString:#"With"];
totalMethodName=[totalMethodName stringByAppendingString:#"Apple"];
SEL s=NSSelectorFromString(totalMethodName);
[self performSelector:s];
will call
-(void)vijayWithApple{
NSLog(#"vijayWithApple called");
}
You can make use of NSInvocation to dynamically bind multiple arguments to a selector. Follow this post to learn it.
If you are going to use NSInvocation you have to define your methods in the objective-C way something like the following.
- (double)firstCalcWithInput1:(double)input1 input2:(double)input2 andInput3:(double)input3;
- (double)secondCalcWithInput1:(double)input1 input2:(double)input2 andInput3:(double)input3;

Using an NSValueTransformer to set values of an NSManagedObject instance

I am using a custom NSValueTransformer to store color information in my Core Data store. The transformation between Transformable data and a UIColor instance works great once the color data is in the store already (ie once the app has been run and quit once already). However when I first run the app and am loading in these values (from a text file) they "stuck" as NSCFStrings.
In this line of code "attributes" is a dictionary has keys which are NSManagedObject attribute names and values that are the expected values for those attributes. In my color example the key value pair is "color":"1,1,1,0.5"
[object setValue:[attributes valueForKey:attribute] forKey:attribute];
The value for "color" will now remain a string in this instance until it's get transformed via my NSValueTransformer and then retransformed into a UIColor when the app gets run again.
I could just do the same transform here that I'm doing in the NSValueTransformer, but this is in a utility class I wrote that could theoretically be used for any transformer. I also thought of finding a way to get all newly created NSManagedObject instances out fo memory thereby forcing the transformation to go through, but that just seems like a hack.
Note: This "hack" works for me and let's me continue, but still feels ugly. Use NSManagedObjectContext's reset method if you're having similar problems / looking for a "just work" solution.
Any ideas?
(I have a hunch this is similar to " Why is my transformable Core Data attribute not using my custom NSValueTransformer? " but outside of the title our problems seem to be different)
Here is my NSValueTransformer
#implementation UIColorRGBValueTransformer
+ (Class)transformedValueClass
{
return [NSData class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
- (id)transformedValue:(id)value
{
return [value dataUsingEncoding:NSUTF8StringEncoding];
}
- (id)reverseTransformedValue:(id)value
{
NSString *colorAsString = [[[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding] autorelease];
NSArray *components = [colorAsString componentsSeparatedByString:#","];
CGFloat r = [[components objectAtIndex:0] floatValue];
CGFloat g = [[components objectAtIndex:1] floatValue];
CGFloat b = [[components objectAtIndex:2] floatValue];
CGFloat a = [[components objectAtIndex:3] floatValue];
return [UIColor colorWithRed:r green:g blue:b alpha:a];
return nil;
}
#end
You're really not using the transformable attribute as intended. Your method should work in theory but it is an ugly kludge.
You usually only have to write a custom value transformer for a system defined class when you want to do something non-standard. In the vast majority cases, the preferred method is to use the default NSKeyedUnarchiveFromDataTransformerName. Any class that implements the NSCoding protocol can use that value transformer.
Since UIColor does implement the NSCoding protocol, you can just set the attribute to 'transformable' and the NSKeyedUnarchiveFromDataTransformerName will populate the transformer name automatically (at least it did in Xcode 3.x.) In use, Core Data will create the appropriate accessors so you can set and get the UIColor attribute just like any other key:
[aMoObject setValue:aUIcolorObj forKey:#"colorAttributeName"];
As a good rule of thumb, the API will do most of the work for you in the case of API classes. If you find yourself working hard with a system class, you've probably missed something.

Using Array Controllers to restrict the view in one popup depending on the selection in another. Not core data based

I am working on an app that is not core data based - the data feed is a series of web services.
Two arrays are created from the data feed. The first holds season data, each array object being an NSDictionary. Two of the NSDictionary entries hold the data to be displayed in the popup ('seasonName') and an id ('seasonID') that acts as a pointer (in an external table) by matches defined for that season.
The second array is also a collection of NSDictionaries. Two of the entries hold the data to be displayed in the popup ('matchDescription') and the id ('matchSeasonId') that points to the seasonId defined in the NSDictionaries in first array.
I have two NSPopUps. I want the first to display the season names and the second to display the matches defined for that season, depending on the selection in the first.
I'm new at bindings, so excuse me if I've missed something obvious.
I've tried using ArrayControllers as follows:
SeasonsArrayController:
content bound to appDelegate seasonsPopUpArrayData.
seasonsPopup:
content bound to SeasonsArrayController.arrangedObjects; content value bound to SeasonsArrayController.arrangedObjects.seasonName
I see the season names fine.
I can obviously follow a similar route to see the matches, but I then see them all, instead of restricting the list to the matches for the season highlighted.
All the tutorials I can find seem to revolve around core data and utilise the relationships defined therein. I don't have that luxury here.
Any help very gratefully received.
This is not an answer - more an extension of the previous problem.
I created MatchesArrayController and subclassed it from NSArrayController to allow some customisation.
Following the example in 'Filtering Using a Custom Array Controller' from 'Cocoa Bindings Topics', I followed the same idea as above:
MatchessArrayController: content bound to appDelegate matchesPopUpArrayData.
matchesPopup: content bound to MatchesArrayController.arrangedObjects; content value bound to MatchesArrayController.arrangedObjects.matchDescription.
I've derived the selected item from seasonPopUp:sender and used this to identify the seasonId.
The idea is to change the arrangedObjects in MatchesArrayController by defining the following in;
- (NSArray *)arrangeObjects:(NSArray *)objects
{
if (searchString == nil) {
return [super arrangeObjects:objects];
}
NSMutableArray *filteredObjects = [NSMutableArray arrayWithCapacity:[objects count]];
NSEnumerator *objectsEnumerator = [objects objectEnumerator];
id item;
while (item = [objectsEnumerator nextObject]) {
if ([[[item valueForKeyPath:#"matchSeasonId"] stringValue] rangeOfString:searchString options:NSAnchoredSearch].location != NSNotFound) {
[filteredObjects addObject:item];
}
}
return [super arrangeObjects:filteredObjects];
}
- (void)searchWithString:(NSString *)theSearchString {
[self setSearchString:theSearchString];
[self rearrangeObjects];
}
- (void)setSearchString:(NSString *)aString
{
[aString retain];
[searchString release];
searchString=aString;
}
I've used NSLog to check that things are happening the way they are supposed to and all seems ok.
However, it still doesn't do what I want.
[self rearrangeObjects]; is supposed to invoke the arrangeObjects method but doesn't. I have to call it explicity
(i.e.[matchesArrayController arrangeObjects:matchesPopUpArrayData]; )
Even then, although filteredObjects gets changed the way it is supposed to, the drop down list does not get updated the way I want it to.

Objective-C - How to implement an array of pointers in a method declaration

Ok, if you take a look at my two previous posts (Link #2 in particular), I would like to ask an additional question pertaining to the same code. In a method declaration, I am wanting to define one of the parameters as a pointer to an array of pointers, which point to feat_data. I'm sort of at a loss of where to go and what to do except to put (NSMutableArray*)featDataArray in the declaration like below and access each object via another pointer of feat_data type. BTW, sorry to be asking so many questions. I can't find some of the things like this in the book I am using or maybe I'm looking in the wrong place?
-(void)someName:(NSMutableArray*)featDataArray;
feat_data *featDataPtr = [[feat_data alloc] init];
featDataPtr = [featDataArray objectAtIndex:0];
Link #1
Link #2
Your declaration looks fine. "NSMutableArray*" is an appropriate type for your parameter. (Objective-C doesn't have generics so you can't declare anything about what's inside the array.)
One problem I see in your code is that you allocate an object for no reason and then throw away the pointer (thus leaking memory).
I don't know what it is that you are trying to do, so here are some things that you can do with an NSMutableArray:
- (void)someName:(NSMutableArray *)featDataArray {
feat_data *featDataPtr = [[feat_data alloc] init];
[featDataArray addObject:featDataPtr]; // add an object to the end
[featDataPtr release];
feat_data *featDataPtr2 = [[feat_data alloc] init];
[featDataArray replaceObjectAtIndex:0 withObject:featDataPtr2]; // replace an existing entry
[featDataPtr2 release];
feat_data *featDataPtr3 = [featDataArray objectAtIndex:0]; // get the element at a certain index
// do stuff with featDataPtr3
}