I need to encode additional data to a NSString (Long story, please don't ask why...)
I've subclassed NSString using the method outlined here:
When I assign one of these subclasses as a UILabel's text I would expect to get it back when asking the labels text. But this isn't the case. (I get an NSString cluster instance instead)
MyString *string = [[MyString alloc] initWithString:#"Some string"];
UILabel *l = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
l.text = string;
NSString *t = l.text; // not getting the "MyString" object
Is there a work around for this?
The label copies the string:
#property (nonatomic, copy) NSString *text
so you at least need to implement copy to return your subclass type and copy your other data.
(not that subclassing is the best idea)
If you subclass NSString, you are braver than I would ever be, and you are totally on your own. NSString is a class cluster. You have basically not a chance in hell to subclass it and get it to work. It starts with the initialisation where [super init] which would be the NSString init method might return any kind of object.
Related
I'm just beginning with XCode and coding for iOS, have the following problem. I want to add a function that sets UITextField values in the ViewController based around the value from a UIStepper. The code actually handles formatting three UITextFields, cut it to one to shorten the example. This code works fine:
- (IBAction)Temp_Stepper_Changed:(UIStepper *)sender {
integer_t stepperValue = (integer_t) sender.value;
NSString *temp_format = [[NSString alloc]
initWithFormat: #"%%.%df",stepperValue];
double fahrenheit = [_TempF_Text.text doubleValue];
NSString *FresultString = [[NSString alloc]
initWithFormat: temp_format,fahrenheit];
_TempF_Text.text = FresultString;
}
I have several places I want to do this, so want to create a function to call, and so I put this function into the view controller's .m file:
void Temp_Text_Update (double F_Temp){
NSString *FresultString = [[NSString alloc]
initWithFormat: #"%.2f",F_Temp];
_TempF_Text.text = FresultString;
}
The function won't compile, results in error:
use of undeclared identifier '_TempF_Text'
Without the line, it compiles fine, can call the function, pass values, etc. I had assumed (remember, beginning at this) as the UIStepper had _TempF_Text in it's scope, the function being in the same .m file would as well. Is there some magic happening behind the scenes that allows the IBAction type calls to access any value from the ViewController items, but my function is missing said magic? I'll also need the UIStepper value to complete the function. This was built using Storyboard, control-drag for outlets and actions, header file is:
#interface TemperatureViewController : UIViewController
#property (weak, nonatomic) IBOutlet UITextField *TempF_Text;
#property (weak, nonatomic) IBOutlet UIStepper *Temp_Stepper;
- (IBAction)TempF_CnvButton:(id)sender;
- (IBAction)Temp_Stepper_Changed:(UIStepper *)sender;
I've spent a few hours searching, including this site, found references from one ViewController to another and so forth, but doesn't really match; tried a few things anyway, but nothing worked (though some yielded extra errors). I suspect it is so obvious and simple as to not be asked, but I've run out of ideas and any help would be appreciated.
To answer the specific question, the correct syntax for the signature you're trying to write would look like this:
- (void)Temp_Text_Update:(double)F_Temp {
This says we have a method named Temp_Text_Update: that takes a double parameter called F_Temp and doesn't return anything.
A more general solution would be to use NSNumberFormatter to go between strings and doubles in a locale sensitive way, something like this:
// get a double from a string
- (double)doubleFromString:(NSString *)string {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *number = [formatter numberFromString:string];
return [number doubleValue];
}
// get a string from a double:
- (NSString *)stringFromDouble:(double)aDouble {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
return [formatter stringFromNumber:[NSNumber numberWithDouble:aDouble]];
}
Then your computing functions would look like this:
NSString *text = self.someControlView.text;
double aDouble = [self doubleFromString:text];
// do computation on aDouble, resulting in
double result = // whatever
[self setOutputTextWithResult:result];
Finally, your method (better named):
- (void) setOutputTextWithResult:(double)result {
self.someControlView.text = [self stringFromDouble:result];
}
It's so short now, it almost doesn't need it's own method.
I have a category on NSString:
- (CGSize) agb_sizeWithFont:(UIFont *)font width:(CGFloat)width lineBreakMode:(NSLineBreakMode)lineBreakMode {
if (!font) return CGSizeZero;
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = lineBreakMode;
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
// this line is not threadsafe
NSAttributedString *as = [[NSAttributedString alloc] initWithString:self attributes:attributes];
CGRect bounds = [as boundingRectWithSize:CGSizeMake(width, 10000) options:(NSStringDrawingUsesLineFragmentOrigin) context:nil];
return CGSizeMake(width, bounds.size.height);
}
I've observed intermittent EXC_BAD_ACCESS crashes when running this code on multiple threads (sometimes in initWithString: attributes:, sometimes in boundingRectWithSize:options:context:).
I believe my code is not thread-safe because self might deallocate on one thread while initWithString: attributes: is executing on the other.
Is my conclusion about this method's thread safety correct?
Would this code make it thread-safe?
NSString *strongSelf = self;
NSAttributedString *as = [[NSAttributedString alloc] initWithString:strongSelf attributes:attributes];
(By maintaining a reference to self, I attempt to ensure the object in memory is not deallocated while I'm using it.)
Is there any way to declare this method as not thread-safe? I'd love to generate a warning, for example, if a string declared as a nonatomic property is passed into this method.
Note: In case it's not obvious, I'm using ARC.
Any NSMutableString is-a NSString, too. Any method you add to NSString in a category can therefore be invoked on an NSMutableString, too. It is not safe to operate on a mutable string on one thread if it might be being mutated at the same time on another thread. It is safe to read a mutable string while it is being read on another thread.
Since your invocation of -[NSAttributedString initWithString:attributes:] is reading from the string self, that's not safe to do if self is a mutable string and might be mutated on another thread at the same time.
You might be tempted to review all calls of -agb_sizeWithFont:width:lineBreakMode: to see if the type of the receiver is NSMutableString*. However, that's not sufficient. An instance of NSMutableString may be pointed to by a variable of type NSString* (or other things like NSObject* or id), too.
You won't be able to make -agb_sizeWithFont:width:lineBreakMode: thread-safe, as such. Thread-safety doesn't decompose. It will have to be designed into the code which manages whatever strings it might be invoked on. That is, all of the code which touches a given string object and might invoke your method and, in other places, might mutate it, must be in charge of making sure that only one of those things is happening at a time.
Your code looks like ARC enabled so I assume it is enabled.
self might deallocate on one thread while initWithString: attributes: is executing on the other.
this is not possible with ARC, because in order to execute agb_sizeWithFont... method, you must doing something like this
NSString *str = /*...*/; // this will create a strong reference to str
[str agb_sizeWithFont:/*...*/]; // even str is released in other thread at this point, it won't be deallocated
NSString *strongSelf = self;
this code just create an extra strong reference to self, but since self must be retained somewhere already, it won't change anything
I don't think you can declare anything to be "not thread-safe" (actually everything are not thread-safe by default) and I don't understand how you want to use such warning.
methods in category have no difference compare to method in #implementation regards to thread-safety.
I've been racking my brain over this seemingly simple issue. I have a XYZObject class where I declare:
#property BOOL checked;
In my View Controller, I import the object and whenever I use 'checked', the app compiles fine but breaks at runtime wherever 'checked' is used, for example:
XYZObject *tableitem = [myDictionary[currentCategory] objectAtIndex:indexPath.row];
if (tableitem.checked) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
This was working fine until I deleted and re-added the XYZObject class, so I've been debugging under the assumption that something in the file path is what's screwing things up. But I can click on 'checked' in my VC and under Quick Help it shows the proper reference to XYZObject. This is the exact error:
[__NSCFString checked]: unrecognized selector sent to instance
EDIT/UPDATE:
With some help I've realized the issue is that when I changed my datasource from manual declaration in the ViewController, to importing a Plist, I completely scrapped my XYZObject and didn't account for it. Here is the original way I declared my dictionary:
XYZCategory *category1 = [[XYZCategory alloc]init]; category1.categoryArray = #"First Category"; [categoryArray addObject:category1];
XYZObject *object1 = [[XYZObject alloc]init]; object1.objectName = #"My String"; [objectArray addObject:object1];
myDictionary[category1.categoryArray] = objectArray;
When I switched to the Plist, the code changed to:
NSString *path = [[NSBundle mainBundle] pathForResource:#"myDictionaryPlist" ofType:#"plist"];
NSMutableDictionary *plistDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
objectArray = plistDictionary[#"First Category"];
myDictionary[category1.categoryArray] = objectArray;
And then for reference, XYZObject makes the following declarations:
#property NSString *objectName;
#property BOOL checked;
So the dictionary problem would be that I'm just pulling the direct strings for the objectArray, instead of a set of XYZObjects. I'm going to keep testing but I'm pretty sure I just have to re-define objectArray to be a set of objects based on what's pulled from the Plist.
But I also think that since I'm using the Plist now to create a dictionary (that is popped into a table where the Keys are sections and Values are rows), I can simplify things by removing the XYZCategory and XYZObject all together. Not sure if that's possible but I'm going to work towards it.
As the error message is suggesting, tableitem is actually a NSString, contrary to what you expect.
You are probably populating the dictionary in the wrong way.
I have an object: indivOrder:
#interface indivOrderDetails : NSObject{
NSNumber* shirtNumber;
NSNumber* pantsNumber;
NSNumber* jacketNumber;
NSNumber* laundryNumber;
NSNumber* blouseNumber;
NSNumber* blazerNumber;
NSNumber* skirtNumber;
NSNumber* suitNumber;
NSString* pickUpOrDropOff;
NSString* pickUpFrom;
NSNumber* totalOrderPrice;
}
They're all given the interface of
#property (nonatomic, retain) NSNumber* propertyName
I have three steps.
First I retrieve the data from a text field:
shirtNumber = [self convertStringToNumber:shirtField.text];
Second, I use this convertStringToNumber method.
-(NSNumber*) convertStringToNumber:(NSString*)stringToConvert
{
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *myNumber = [f numberFromString:stringToConvert];
return myNumber;
}
Then I assign that value to my object variable.
orderDetails.shirtNumber = shirtNumber;
But the only value I'm coming back with when I try to access the orderDetails.shirtNumber variable is zero. The shirtNumber is coming back with the correct value from the ViewController.
Your problem likely lies here:
shirtNumber = [self convertStringToNumber:shirtField.text];
Look at that to which you're assigning. It's one of the ivars you declared. That ivar, despite what you may think, is not the backing for your property of the same name. The backing instead would be
_shirtNumber
since you don't appear to have synthesized (i.e., used the #synthesize directive) any accessors.
As a result, _shirtNumber and shirtNumber are two distinct entities, which means that when you attempt to access
orderDetails.shirtNumber
of course it's going to be nil. You never put anything in it.
So you have two choices: Either use the property name that's prefixed with an underscore
_shirtNumber = [self convertStringToNumber:shirtField.text];
or use #synthesize to set up some accessors, in which case you'd do this
self.shirtNumber = [self convertStringToNumber:shirtField.text];
or this (if you like the old form)
[self setShirtNumber:[self convertStringToNumber:shirtField.text]];
Guess that's three choices. Anyway, it's a subtle, classic 'gotcha.' If you want further background for this, you can find it in this very excellent explanation elsewhere on Stack Overflow.
Good luck to you in your endeavors.
Hi a very simple app it takes in 2 arguments via 2 text boxes, and then totals them and displays them in a label called result. The idea is to have it handled via an object called brain, for which in the later part i have given the code. problem is foo is zero and when you click the button the result goes to nothing.
The plan is to use this to build a better model view architecture for a bigger app i have completed.
#import "calbrain.h"
#import "ImmyViewController.h"
#interface ImmyViewController ()
#property (nonatomic, strong) calbrain *brain;
#end
#implementation ImmyViewController
#synthesize brain;
#synthesize num1;
#synthesize num2;
#synthesize result;
-(calbrain *) setBrain
{
if (!brain) {
brain = [[calbrain alloc] init];
}
return brain;
}
- (IBAction)kickit:(UIButton *)sender {
NSString *number1 = self.num1.text;
NSString *number2 = self.num2.text;
NSString *foo;
foo = [brain calculating:number1 anddouble:number2];
self.result.text = foo;
// self.result.text = [brain calculating:self.num1.text anddouble:self.num2.text];
}
#end
#implementation calbrain
-(NSString *) calculating:(NSString *)number1 anddouble:(NSString *)number2
{
double numb1 = [number1 doubleValue];
double numb2 = [number2 doubleValue];
double newresult = (numb1 + numb2);
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
NSString *numberAsString = [numberFormatter stringFromNumber:[NSNumber n numberWithFloat:newresult]];
return numberAsString;}
Check your brain using NSLog in the (IBAction)kickit:(UIButton *)sender function. I guess you didn't initialise brain. If this is not the case, you need to provide more code.
i did just that, i came to the conclusion the setter for brain isnt working properly
i put the alloc init line of code before i needed to alloc init the brain, and it works fine, i stubbed out the setter,
i will go back and see why it wasnt overriding the setter made by properties, but interesting stuff none the less. it means i can change my actual larger app to have a cleaner more organised architecture.
thanks for your time.
Try initializing your brain object in viewDidLoad() using your setter method. You have to call setter method to get your brain object initialized.
Something like this
viewDidLoad()
{
brain = [self setBrain];
//You can also do this
brain = [[calbrain alloc] init];
}
and use that brain object in your (IBAction)kickit: method.
Hope this helps.