I'm very new to objective-c and I'm getting a basic error and unsure on how to fix it.
for(ZBarSymbol *sym in syms) {
resultText.text = sym.data; //sym.data = 0012044012482
[self phpPost:(int)sym.data];
break;
}
}
- (void)phpPost: (int)barcode {
NSString *theValue = [NSString stringWithFormat:#"%#", barcode]; //problem line
labelScan.text = theValue;
//labelScan.text = #"Barcode scanned";
}
when i use #"%#" the text of the label is correct (0012044012482), but when i use #"%d" it isn't (random number every time i restart the app). I would like to use #"%#" (or something that works) but for some reason xCode is giving me an error. and I'm unsure on how to fix the error.
The error is:
Format specifies type 'id' but the argument has type 'int'
In the end I plan on having that code (sym.data) written to a MySQL database using the POST method.
You can't just convert it to an int by casting if it's an object (which it must be if the %# format specifier isn't causing a crash).
Assuming from the fact that you're assinging it directly to a label's text that it's an NSString, you should either change the parameter type of phpPost:
- (void)phpPost: (NSString *)barcode {
labelScan.text = barcode;
}
or extract the intValue before passing sym.data:
[self phpPost:[sym.data intValue]];
and then use the proper %d format specifier in phpPost:.
Your barcode isn't an int, it is an NSString. Instead of doing (int)sym.data, pass in [sym.data intValue]. That should correctly convert it to an integer.
The reason you get a random number is because you can't just cast a string object to a primitive data type :)
I don't know what type sym.data is, but it is likely a pointer to an object, and not the value itself. You cast that pointer to int, so when you are using %d you are effectively printing the memory location of the object. That is why it changes each time you run the program (Objective-C let's you do this without any warnings - something to watch out for).
To fix this, either extract the integer value you need from the sym.data object using it's properties; or pass the object as a pointer. For instance, you could try calling your method like this:
[self phpPost:sym.data];
And then change your method to be:
- (void)phpPost: (id)barcode {
NSString *theValue = [NSString stringWithFormat:#"%#", barcode];
labelScan.text = theValue;
}
Ok, I did some thinking while I was at work today, and I figured out that an INT isn't going to work for me. if I make that object to an int, I would loss some data that is vital to what I'm doing. eg. object=001234 int=1234. I need the zeros. So, in the end, I'm keeping it an object (string) and just passing it into the function.
Here is my code after I got it working correctly.
for(ZBarSymbol *sym in syms) {
resultText.text = sym.data;
[self phpPost:sym.data];
break;
}
}
- (void)phpPost: (NSString *)barcode {
labelScan.text = barcode;
//labelScan.text = #"Barcode scanned"; //My custon label
}
Thanks, everyone for your responses. Your answer will not go unused. I'm sure I'll be needing this information here soon.
O, if you see that I did this wrong, or not the correct way, please make a comment and tell me .
Related
I have converted an NSArray to NSString using below code
Suppose i have an array with some data in it.
NSString *sample=[array description];
NSLog(#"%#",sample);
which prints:
(
{
URL = "1a516af1a1c6020260a876231955e576202bbe03.jpg##37911944cc1ea8fd132ee9421a7b3af326afcc19.jpg";
userId = 0;
wallpaperId = 31;
},
{
URL = "a9356863fa43bc3439487198283321622f88e31f.jpg##f09c743ebdc26bb9f98655310a0529b65a472428.jpg";
userId = 0;
wallpaperId = 30;
}
)
It looks like array but it is actually a string.
Now I am wondering, how can I reconvert back to NSArray?
Help appreciated.
And please this not a duplicate question, I couldn't found the same anywhere on SO.
You cannot rely on the results of description as it is not a convert to string operator, but merely a debugging aid. There is nothing to stop it changing between O/S releases and there is no equivalent fromDescription method.
The conventional way of serializing an Objective-C collection to and from a string is to use JSON, so look at the NSJSONSerialization class.
the
NSString componentsseparatedbystring
method will return an array of components separated by a string
I wan't to "know" how to put a string at a location in another string. I already know because I figured out another way to do this. But I wan't to know the real way, does it even exist?
I'm also asking this question for future questions on how to put this string at a location in another string the "false" way (in case it can't be done the real way)
What I mean about putting a (sub)string at a location of string is for example to put
this string:#"Hello" at location:5 inString:#"123456789"
I want the results to be:#"12345Hello6789"
Can this be done the real way? something like this fake code:
[str stringByPuttingString:#"s" atLocation:5];//this code does not exist
I figured out other ways to get this done, can we get it to shorter code?
-(NSString *)putString:(NSString *)str atLocation:(int)location ofString:(NSString *)mainString {
NSRange range = NSMakeRange(location, 0);
return [mainString stringByReplacingCharactersInRange:range withString:str];
}
and
-(NSString *)putString:(NSString *)str atLocation:(int)location ofString:(NSString *)mainString {
NSString *first = [mainString substringToIndex:location];
NSString *last = [mainString substringFromIndex:location];
return [NSString stringWithFormat:#"%#%#%#", first, str, last];
}
The first one feels best, any other ideas or real ideas?
Jonathan,
in future cases of this "problem".
Why not just use an NSMutableString?
NSMutableString *string = [NSMutableString stringWithString:#"123456789"];
[string insertString:#"Hello" atIndex:5];
NSLog(#"%#", string);
Outputs:
12345Hello6789
You can use NSMutableString to accomplish this task. Specifically, see the reference to the insertString:atIndex: method which will do exactly what you want, i.e. insert a string into another string at a specified location. API LINK
you can implement this by using NSMutableString method insertString:atIndex:
Inserts into the receiver the characters of a given string at a given location.
- (void)insertString:(NSString *)aString atIndex:(NSUInteger)anIndex
Parameters
aString
The string to insert into the receiver. aString must not be nil.
anIndex
The location at which aString is inserted. The location must not exceed the bounds of the receiver.
Taken from apple developer classes ref
I'm currently working on a project where the user defines some parameters in a NSDictionnary, that I'm using to setup some objects.
For example, you can ask to create a Sound object with parameters param1=xxx, param2=yyy, gain=3.5 ... Then an Enemi object with parameters speed=10, active=YES, name=zzz ...
{
active = NO;
looping = YES;
soundList = "FINAL_PSS_imoverhere_all";
speed = 100.0;
}
I then instantiate my classes, and would like to set the ivars automatically from this dictionnary.
I've actually wrote some code to check that this parameter exists, but I'm having trouble in actually setting the parameter value, especially when the parameter is non object (float or bool).
Here's what I'm doing so far :
//aKey is the name of the ivar
for (NSString *aKey in [properties allKeys]){
//create the name of the setter function from the key (parameter -> setParameter)
NSString *setterName = [aKey stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[aKey substringToIndex:1] uppercaseString]];
setterName = [NSString stringWithFormat:#"set%#:",setterName];
SEL setterSelector = NSSelectorFromString(setterName);
//Check if the parameter exists
if ([pge_object respondsToSelector:setterSelector]){
//TODO : automatically set the parameter
}
else{
[[PSMessagesChecker sharedInstance]logMessage:[NSString stringWithFormat:#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]] inColor:#"red"];
NSLog(#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]);
}
}
}
As you can see, I don't know what to do once I've found that the parameter exists on the object. I tried to use "performSelector... withObject..., but my problem is that some of the parameters are non-objects (float or bool).
I also tried to get the class of the parameter, by using the setter, but it didn't help.
Did anyone manage to do something like that?
Jack Lawrence's comment is spot on.
What you are looking for is called Key Value Coding, or just KVC.
This fundamental part of Cocoa lets you get and set any instance variable using its name as a String and a new value.
It will automatically handle coercing Objects to primitive values, so you can use it for int and float properties too.
There is also support for validating values and handling unknown properties.
see the docs
your code, without validation, could be written
for( id eachKey in props ) {
[anOb setValue:props[eachKey] forKey:eachKey];
}
or just
[anOb setValuesForKeysWithDictionary:props];
as Jack said.
For the non-object parameters you have to put them into an object, for example NSNumber or NSValue. You can then add these objects into your dictionary.
For Example:
float f = 0.5;
NSNumber f_obj = [NSNumber numberWithFloat:f];
To ensure that a formatted string returned by NSString initWithFormat:arguments: is as expected, I need to determine if there are the same number of format specifiers as arguments. Below is a (slightly contrived and highly edited) example:
- (void)thingsForStuff:(CustomStuff)stuff, ...
{
NSString *format;
switch (stuff)
{
case CustomStuffTwo:
format = #"Two things: %# and %#";
break;
case CustomStuffThree:
format = #"Three things: %#, %#, and %#";
break;
default:
format = #"Just one thing: %#";
break;
}
va_list args;
va_start(args, method);
// Want to check if format has the same number of %#s as there are args, but not sure how
NSString *formattedStuff = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
NSLog(#"Things: %#", formattedStuff);
}
Using this method, [self thingsForStuff:CustomStuffTwo, #"Hello", #"World"] would log
"Two things: Hello and World"
...but [self thingsForStuff:CustomStuffTwo, #"Hello"] would log
"Two things: Hello and "
...something that would be preferred to be caught before it happens.
Is there a way to count the format specifiers in a string, preferably something lightweight/inexpensive?
Well, I created my own regex, I have no idea if it's going to catch all of them, and may end finding some false positives, but seems to be working for me:
static NSString *const kStringFormatSpecifiers =
#"%(?:\\d+\\$)?[+-]?(?:[lh]{0,2})(?:[qLztj])?(?:[ 0]|'.{1})?\\d*(?:\\.\\d+)?[#dDiuUxXoOfeEgGcCsSpaAFn]";
You can count the number of arguments using:
NSRegularExpression *regEx = [NSRegularExpression regularExpressionWithPattern: kStringFormatSpecifiers options:0 error:nil];
NSInteger numSpecifiers = [regEx numberOfMatchesInString: yourString options:0 range:NSMakeRange(0, yourString.length)];
Is there a way to count the format specifiers in a string, preferably
something lightweight/inexpensive?
Nope -- really isn't. At least, not if you want it to work across all possible format strings. You would have to duplicate the parser that is used by stringWithFormat:. I.e. don't try to validate everything.
You could count the number of %, but that would not catch things like %% or other special cases. That may be good enough for your purposes.
Because of the way C and Objective-C handle variadic functions/methods like yours, you cannot in general tell how many arguments the user has provided.
Here are two ways to handle your situation.
First, look for another way to do this. The number of arguments you pass to the method is determined at compile-time. So maybe instead of using a variadic method, you should just have three methods:
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing;
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing1 thing:(Thing *)thing2;
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing1 thing:(Thing *)thing2 hatWearer:(Cat *)cat;
And you select the right method to call at compile-time based on how many arguments you want to pass, eliminating the switch statement entirely.
Second, I see that your predefined format strings only use the %# format. Does this mean that you expect the user to only pass objects to your method (aside from the (CustomStuff)stuff argument)?
If the user will only pass objects to your method, and you require those arguments to be non-nil, then you can get the compiler to help you out. Change your method to require the user to pass nil at the end of the argument list. You can tell the compiler that the argument list has to be nil-terminated by declaring the method (in your #interface) like this:
#interface MyObject : NSObject
- (void)thingsForStuff:(CustomStuff)stuff, ... NS_REQUIRES_NIL_TERMINATION
#end
Now the compiler will warn the user “Missing sentinel in method dispatch” if he calls your method without putting a literal nil at the end of the argument list.
So, having changed your API to require some non-nil arguments followed by a nil argument, you can change your method to count up the non-nil arguments like this:
- (void)thingsForStuff:(CustomStuff)stuff, ... {
int argCount = 0;
va_list args;
va_start(args, stuff);
while (va_arg(args, id)) {
++argCount;
}
va_end(args)
int expectedArgCount;
NSString *format;
switch (stuff) {
case CustomStuffTwo:
expectedArgCount = 2;
format = #"Two things: %# and %#";
break;
case CustomStuffThree:
expectedArgCount = 3;
format = #"Three things: %#, %#, and %#";
break;
// etc.
}
NSAssert(argCount == expectedArgCount, #"%# %s called with %d non-nil arguments, but I expected %d", self, (char*)_cmd, argCount, expectedArgCount);
va_start(args, stuff);
NSString *formattedStuff = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
NSLog(#"Things: %#", formattedString);
}
You could count the number of format specifiers, but IIRC you will never be able to count the number of arguments passed into a variable-argument method. This is because of the way C pushes arguments on the stack without specifying how many it has pushed.
Most functions overcome this by requiring that the last argument be nil or some kind of terminator (see [NSArray arrayWithObjects:]). There's even a macro that allows the compiler to check this and emit a warning at compile time.
You can use NS_FORMAT_FUNCTION at the end of your function prototype, as in stringWithFormat method of NSString.
So your method's prototype should be like this:
- (void)thingsForStuff:(CustomStuff)stuff, ... NS_FORMAT_FUNCTION(1,2);
long specifierCount = [myFormatString componentsSeparatedByString:#"%"].count;
This will get you close. Its just a simple split. You would have to account for escaped % values.
I've got object_getInstanceVariable to work as here however it seems to only work for floats, bools and ints not doubles. I do suspect I'm doing something wrong but I've been going in circles with this.
float myFloatValue;
float someFloat = 2.123f;
object_getInstanceVariable(self, "someFloat", (void*)&myFloatValue);
works, and myFloatValue = 2.123
but when I try
double myDoubleValue;
double someDouble = 2.123f;
object_getInstanceVariable(self, "someDouble", (void*)&myDoubleValue);
I get myDoubleValue = 0. If I try to set myDoubleValue before the function eg. double myDoubleValue = 1.2f, the value is unchanged when I read it after the object_getInstanceVariable call. Setting myIntValue to some other value before the getinstancevar function above returns 2 as it should, ie. it has been changed.
then I tried
Ivar tmpIvar = object_getInstanceVariable(self, "someDouble", (void*)&myDoubleValue);
If I do ivar_getName(tmpIvar) I get "someDouble", but myDoubuleValue = 0 still! Then I try ivar_getTypeEncoding(tmpIvar) and I get "d" as it should be.
So to summarize, if typeEncoding = float, it works, if it is a double, the result is not set but it correctly reads the variable and the return value (Ivar) is also correct.
I must be doing something basic wrong that I cant see so I'd appreciate if someone could point it out.
object_getInstanceVariable is a confused little function. It is documented that the last parameter is a void ** parameter—that is, you pass the address of a void * variable and get a pointer to the instance variable—but it is implemented as if it was a void * parameter—that is, you pass the address of the variable that you want to hold a copy of the instance variable. The problem is that the implementation ignores the size of the instance variable and just does a pointer copy. So anything that's the same size as a pointer will work perfectly. If you're running on a 32-bit architecture, only the high 32 bits will be copied. (You should witness the same behavior with a long long instance variable as well.)
The solution is to use the primary API, key-value coding, using -valueForKey:.
The other solution: If you wanted to write a fixed version, say as a category for NSObject, it would look something like this:
#implementation NSObject (InstanceVariableForKey)
- (void *)instanceVariableForKey:(NSString *)aKey {
if (aKey) {
Ivar ivar = object_getInstanceVariable(self, [aKey UTF8String], NULL);
if (ivar) {
return (void *)((char *)self + ivar_getOffset(ivar));
}
}
return NULL;
}
#end
Then your code would look like this:
double myDoubleValue = *(double *)[self instanceVariableForKey:#"someDouble"];
What about using valueForKey:?
NSNumber * value = [self valueForKey:[NSString stringWithUTF8String:ivar_getName(tmpIvar)]];
NSLog(#"Double value: %f", [value doubleValue];
Note: this requires you to have a "someFloat" method. If you want to use setValue:forKey:, you'll also need the "setSomeFloat:" method. This is easily implemented by declaring the ivar as an #property and synthesizing it.