NSAttributedString in a TableView cell - sometimes displayed as plain text - ios

I have a UITextView in my tableview cell. This is how I assign my NSAttributedString to its content:
- (void)setAttributedMessage:(NSAttributedString *)msg
{
self.bubbleView.textView.text = nil;
self.bubbleView.textView.font = nil;
self.bubbleView.textView.textColor = nil;
self.bubbleView.textView.textAlignment = NSTextAlignmentLeft;
self.bubbleView.textView.attributedText = msg;
self.bubbleView.textView.userInteractionEnabled = NO;
}
And my attributed string contains a message text and a date in smaller font by it, so it's how I create it:
- (NSAttributedString *) attributedTextForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *txt = #"Some text";
NSMutableAttributedString *a = [[NSMutableAttributedString alloc] initWithString:txt attributes:#{NSFontAttributeName: [UIFont systemFontOfSize:16]}];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.timeStyle = NSDateFormatterShortStyle;
df.dateStyle = NSDateFormatterNoStyle;
NSString *timestamp = [df stringFromDate:[NSDate date]];
[a appendAttributedString:[[NSAttributedString alloc]
initWithString:[NSString stringWithFormat:#" %#", timestamp]
attributes:#{NSFontAttributeName: [UIFont italicSystemFontOfSize:10]}]];
return [[NSAttributedString alloc] initWithAttributedString:a];
}
What I'm getting, when displaying the Table View, is a plain text - no change of font size or no italics with date. When the cell is redisplayed though it gets displayed properly. So When I scroll the cell out of the screen and get it back there, it's OK. I could also reload the table view to force re-displaying, but this solution sounds like a really lame one.
Any idea what's going on there?

You may not like it, but the best solution is really the "lame" one:
[yourTableView reloadData];
That way, new views will be generated for all (visible) entries, especially the on-screen ones.

Here was my solution:
this forces the label to update on the next operation cycle (I imagine it needs to do some sort of setup in the first instance)
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[cell.label setAttributedText:cellString];
}];

I fixed it by setting table data in viewDidLoad and then refreshing my table view inside viewDidAppear method.

Related

Multiple text alignment with same label using NSMutableAttributedString on iOS

I want to set Text direction for my label using NSMutableAttributedString. for example, I have chat view, which contains message and time. I want to set left alignment for message and right alignment for time using UILabel.
I have used following code, but it's not working,
NSString *Time = [Functions stringFromGivenDate:msg.time withFormate:#"hh:mm a"];
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:#"%#\n%#",msg.text,Time]];
NSDictionary *attrDictionary = #{NSWritingDirectionAttributeName:#[#(NSTextWritingDirectionOverride)]};
[str addAttributes:attrDictionary range:NSMakeRange(msg.text.length+1, Time.length)];
if I understood correctly, this should help:
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.alignment = alignment;// type NSTextAlignment
NSDictionary *attributtes = #{NSParagraphStyleAttributeName : style,};

UIButton Title not updating all the time

I am programmatically changing the title of a UIButton to display count of items in a NSMutableArray.
Sometimes, the title does not update as I add items to the array. When I do the NSLog of what the title should be, it is correct but the button title does not update all the time.
Can anyone spot a problem with my code?
- (void) refreshAfterBlueToothScan
{
/*************************************Refresh Displayed Total********************/
//[_manualBCtemporaryCartArray26 removeAllObjects];
//_manualBCtemporaryCartArray26 = [[NSMutableArray alloc] init];
[localTempArray removeAllObjects];
if (!localTempArray)
{
localTempArray = [[NSMutableArray alloc] init];
}
// Get the DBAccess object;
DBAccess *dbAccess1 = [[DBAccess alloc] init];
// Get the products array from the database
//Get the latest NSMutableArray
localTempArray = [dbAccess1 getProductsFromTmpSales];
// Close the database because we are finished with it
[dbAccess1 closeDatabase];
//Count How many products in array
int iNumberofArrayProducts;
iNumberofArrayProducts = [localTempArray count];
NSString* productCount = [NSString stringWithFormat:#"%i", iNumberofArrayProducts];
NSLog(#"The NUMBER OF TmpTable PRODUCTS is %#",productCount);
//Sum the selling price of the records in the _manualBCtemporaryCartArray26
amountSum = [localTempArray valueForKeyPath:#"#sum.lTotalSellingPrice"];
NSLog(#"The total TmpTable SELLING PRICE is %#",amountSum);
NSNumberFormatter * formatter = [[NSNumberFormatter alloc]init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter setMaximumFractionDigits:2]; // Set this if you need 2 digits
[formatter setMinimumFractionDigits:2]; // Set this if you need 2 digits
NSString * newString = [formatter stringFromNumber:[NSNumber numberWithFloat:[amountSum floatValue]]];
NSLog(#"FORMATTED MONTHLY SALES IS,R%#",newString);
//This is a hack that replaces the comma with a dot. I want to display the Price as R2 000.10
NSCharacterSet *doNotWant = [NSCharacterSet characterSetWithCharactersInString:#"/:,"];
newString=[[newString componentsSeparatedByCharactersInSet: doNotWant] componentsJoinedByString: #"."];
NSString *item = [NSString stringWithFormat:#"R%# (%i)",newString, iNumberofArrayProducts];
/**********Play audio to warn user that item was added to cart*******/
// ivar
SystemSoundID mBeep;
// Create the sound ID
NSString* path = [[NSBundle mainBundle]
pathForResource:#"scanBarCode" ofType:#"mp3"];
NSURL* url = [NSURL fileURLWithPath:path];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &mBeep);
// Play the sound
AudioServicesPlaySystemSound(mBeep);
// Dispose of the sound
//AudioServicesDisposeSystemSoundID(mBeep);
/**********END - Play audio to warn user that item was added to cart*******/
//When user scans the barcode, we want to display the product that was just scanned. This allows user to see what product they just scanned as it is automatically loaded on the shopping cart. Put IF Statement so this code only executes after the scanning barcode.
[self.searchDisplayController setActive: YES animated: YES];
//self.searchDisplayController.searchBar.hidden = NO;
self.searchDisplayController.searchBar.text = [NSString stringWithFormat:#"%i",fklInventoryID];
[self.searchDisplayController.searchBar becomeFirstResponder];
[self.searchDisplayController.searchBar resignFirstResponder];
NSLog(#"BUTTON ITEM IS,%#",item);
[manualTotalPriceBtn setTitle: item forState: UIControlStateNormal];
/*************************************END - Refresh Displayed Total********************/
}
Try this:
[manualTotalPriceBtn setAttributedTitle:nil forState:UIControlStateNormal];
[manualTotalPriceBtn setTitle:item forState:UIControlStateNormal];
If you are using a xib file the IB will set your value for the attributedTitle instead of the title.
try changing button type from system to custom.
Check that you update the button on the mainthread. If your method is called from a callback of whatever bluetooth scanner module you are using, it might be happening on a backround thread. This will cause button to sometimes update, sometimes it will not, or later.
If not, check the superview property of the button to not be nil. Maybe somewhere in your code you create a bew button, add it to the superview but forgot update your property to point to it (or some other scenario where you update a button that is not inserted in the view).

Passing parameters makes the code run "slower"?

Background Information
Currently I'm setting the text for each UITableViewCell in my UITableView using the following code:
Scenario A:
cell.textLabel.attributedText = [news formattedSubject];
However, consider if I were to add a parameter for the formattedSubject definition, just a single integer parameter so the code is now:
Scenario B:
cell.textLabel.attributedText = [news formattedSubject:1];
The text in each table view cell is roughly 3-5 lines in length, and is read from an external source and parsed via JSON. Here's a diagram of the desired result, which is what happens in Scenario A:
Scenario A Flow Diagram:
Image A simply displays the default, empty UITableView that I get when the app is still loading the JSON data. After the app retrieves and parses this data, it then populates the data into the UITableView, which results in Image B. This is the desired (and expected) result.
However, if I add a parameter to formattedSubject, I instead get the flow diagram below:
Scenario B Flow Diagram:
Once again, Image A displays the default UITableView. However, it is what happens in Image B that is the problem. In Image B, the data has been parsed, but has not yet been formatted properly by formattedSubject, thus resulting in a single, horizontally-narrow, and lengthy row of unformatted text. After a fraction of a second, the app looks like Image C, the end result which displays the formatted data after it has been parsed.
My question:
The only change I made is the addition of a parameter to formattedSubject. That is, I changed -(NSAttributedString*)formattedSubject { to -(NSAttributedString*)formattedSubject:(int)state {. It doesn't matter that there is nothing within formattedSubject that actually uses the state integer, I'm still getting the results from Scenario B.
This change seems to make the code run more slowly. It creates a delay between when the data is parsed and when it is formatted and displayed in the UITableView. I'm curious as to why this is, and how I can fix/circumvent this issue.
Aside from being an aesthetics issue, what happens in Scenario B also interferes with my automatic loading of new data when the user reaches the end of the UITableView. Because of horizontally-narrowed rows of text, the last row of data will momentarily be displayed in the UITableView when it is first loaded, thus causing data to be retrieved twice upon app startup.
I am nowhere close to an expert in coding, and thus it makes absolutely no sense to me how simply adding a parameter to my NSAttributedString could create the aforementioned delay. I would be very appreciative if someone could:
Explain why this is happening, and
Offer a solution to resolve this issue.
Thank you very much for reading this, any and all comments/help is welcomed.
Edit 1: #Vijay-Apple-Dev.blogspot.com, #txulu
Here is my formattedSubject code:
-(NSAttributedString*)formattedSubject:(int)state {
if(formattedSubject!=nil) return formattedSubject;
NSDictionary *boldStyle = [[NSDictionary alloc] init];
if(state==1) {
boldStyle = #{NSFontAttributeName:[UIFont fontWithName:#"Helvetica-Bold" size:16.0],NSForegroundColorAttributeName:[UIColor colorWithRed:0.067 green:0.129 blue:0.216 alpha:1.0]};
}
else {
boldStyle = #{NSFontAttributeName:[UIFont fontWithName:#"Helvetica-Bold" size:16.0],NSForegroundColorAttributeName:[UIColor whiteColor]};
}
NSDictionary* normalStyle = #{NSFontAttributeName: [UIFont fontWithName:#"Helvetica" size:14.0]};
NSMutableAttributedString* articleAbstract = [[NSMutableAttributedString alloc] initWithString:subject];
[articleAbstract setAttributes:boldStyle range:NSMakeRange(0, subject.length)];
[articleAbstract appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n"]];
int startIndex = [articleAbstract length];
NSTimeInterval _interval=[datestamp doubleValue];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:_interval];
NSDateFormatter *_formatter=[[NSDateFormatter alloc]init];
[_formatter setDateFormat:#"MM/dd/yy"];
NSString* description = [NSString stringWithFormat:#"By %# on %#",author,[_formatter stringFromDate:date]];
[articleAbstract appendAttributedString:[[NSAttributedString alloc] initWithString: description]];
[articleAbstract setAttributes:normalStyle range:NSMakeRange(startIndex, articleAbstract.length - startIndex)];
formattedSubject = articleAbstract;
return formattedSubject;
}
Please note that as I said before, even if I don't actually use the state parameter, I still get the same results.
Here is my cellForRowAtIndexPath code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
News *news = newsArray[indexPath.row];
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
if([selectedIndexPath isEqual:indexPath]) {
cell.textLabel.attributedText = [news formattedSubject:1];
}
else {
cell.textLabel.attributedText = [news formattedSubject:0];
}
cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
UIView *selectedBackgroundViewForCell = [UIView new];
[selectedBackgroundViewForCell setBackgroundColor:[UIColor colorWithRed:0.169 green:0.322 blue:0.525 alpha:1.0]];
cell.selectedBackgroundView = selectedBackgroundViewForCell;
cell.textLabel.highlightedTextColor = [UIColor whiteColor];
if (indexPath.row == [newsArray count] - 1) {
[self parseJSON];
}
return cell;
}
Please let me know if I can post anything else that may help.
Edit 2:
I'm not exactly sure if there is a performance issue. Upon further testing, I am inclined to believe that in Scenario A, the app loads and formats the cell data before displaying it, while in Scenario B, the app loads the data, displays it in the UITableViewCell, and then formats it, which creates the problem I detailed above.
Some people have brought up the code in my parseJSON method, so I'm posting it here for reference. As you can see I do indeed implement multithreading in order to prevent the data loading from lagging the application.
-(void)parseJSON
{
loading.alpha = 1;
loading.image = [UIImage imageNamed:#"loading.png"];
activityIndicator.alpha = 1;
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(checkLoading) userInfo:nil repeats:YES];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
parseNumber = parseNumber + 1;
int offset = parseNumber*20-1;
NSString *URLString = [NSString stringWithFormat:#"http://feedurl.com/feed.php?offset=%d",offset];
NSURL *url=[NSURL URLWithString:URLString];
NSData *data=[NSData dataWithContentsOfURL:url];
NSError* error;
if(data!=nil) {
json = [NSJSONSerialization JSONObjectWithData:data options: NSJSONReadingMutableContainers error: &error];
for(NSDictionary *newsInfo in json) {
News *newsList = [[News alloc] init];
newsList.thread = newsInfo[#"thread"];
newsList.author = newsInfo[#"author"];
newsList.subject = newsInfo[#"subject"];
newsList.body= newsInfo[#"body"];
newsList.datestamp = newsInfo[#"datestamp"];
[jsonTemp addObject:newsList];
}
newsArray = jsonTemp;
}
dispatch_sync(dispatch_get_main_queue(), ^{
if(data!=nil) {
[newsTable reloadData];
}
else {
activityIndicator.alpha = 0;
loading.image = [UIImage imageNamed:#"error.png"];
[self startTimer];
}
});
});
}
Edit:
Okay, there's a difference when calling [news formattedSubject] instead of [news formattedSubject:1]. The first one is like doing news.formattedSubject, this is, access the formattedSubject property that returns the ivar immediately, pretty fast. The second one calls the more complex formattedSubject: method that executes the code you posted, slower.
Original:
Your code seems fine except for some minor details like:
NSDictionary *boldStyle = [[NSDictionary alloc] init];
not being necessary because you assign just afterwards:
boldStyle = #{NSFontAttributeName ...}
Also, what I guess could be causing your problem is:
if (indexPath.row == [newsArray count] - 1) {
[self parseJSON];
}
Calling this inside your cellForRowAtIndexPath: could be a severe performance problem. If this method does a lot of work and does not do it in a background it could cause the delays you mention. As a rule of thumb, you should never do network/data processing in the main thread (cellForRowAtIndexPath will always be called in that thread by the system).
You says like below
"The text in each table view cell is roughly 3-5 lines in length, and is read from an external source and parsed via JSON. Here's a diagram of the desired result, which is what happens in Scenario A:"
I assume that you are reading data from
1.Local Core data Database
or
2.Web server's database.
For case 1, you should use NSFetchedResultsController, follow up this tutorial
https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html
http://www.raywenderlich.com/999/core-data-tutorial-for-ios-how-to-use-nsfetchedresultscontroller
For case 2 you should do in background thread,and update it by Main thread in tableview, when it is available, follow up this tutorial
How to load JSON asynchronously (iOS)

Core Text in UITableviewCell's content overlapping and repeating and superimpose on the other cells

I am using Core Text to add text to UITableviewCell's content but arabic content seems to be overlapping and repeating itself as I scroll and superimpose on the other cells.
I am also using other elements on the page which appear just fine and are not repeating . Just the Core Text seems to be repeating.
I cant figure out why .
Here is my code:
- (CTFontRef)newCustomFontWithName:(NSString *)aFontName
ofType:(NSString *)type
attributes:(NSDictionary *)attributes {
NSString *fontPath = [[NSBundle mainBundle] pathForResource:aFontName ofType:type];
NSData *data = [[NSData alloc] initWithContentsOfFile:fontPath];
CGDataProviderRef fontProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
CGFontRef cgFont = CGFontCreateWithDataProvider(fontProvider);
CGDataProviderRelease(fontProvider);
CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attributes);
CTFontRef font = CTFontCreateWithGraphicsFont(cgFont, 0, NULL, fontDescriptor);
CFRelease(fontDescriptor);
CGFontRelease(cgFont);
return font;
}
- (CATextLayer *)customCATextLayer:(NSString *)textString {
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:24.f], (NSString *)kCTFontSizeAttribute,
[NSNumber numberWithInt:1], (NSString *)kCTLigatureAttributeName,
nil];
CTFontRef font = [self newCustomFontWithName:#"me_quranKer6"
ofType:#"ttf"
attributes:attributes];
CATextLayer *normalTextLayer = [[CATextLayer alloc] init];
normalTextLayer.font = font;
normalTextLayer.string = textString;
normalTextLayer.wrapped = YES;
normalTextLayer.foregroundColor = [[UIColor blackColor] CGColor];
normalTextLayer.fontSize = 24.f;
normalTextLayer.alignmentMode = kCAAlignmentCenter;
normalTextLayer.frame = CGRectMake(0.f, 10.f, 320.f, 32.f);
CFRelease(font);
return normalTextLayer;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
QuranVersesViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"verseCell"];
Verse *verse = [self.fetchedResultsController objectAtIndexPath:indexPath];
//English Content starts
NSMutableAttributedString * englishAttributedString;
if (!englishAttributedString)
englishAttributedString = [[NSMutableAttributedString alloc] initWithString:#""];
NSMutableAttributedString * englishSubtitleAttributedString;
NSMutableAttributedString * englishVerseAttributedString;
if (!englishVerseAttributedString)
englishVerseAttributedString = [[NSMutableAttributedString alloc] initWithString:verse.english_version];
NSMutableAttributedString * englishFootnoteAttributedString;
if (!englishFootnoteAttributedString)
englishFootnoteAttributedString = [[NSMutableAttributedString alloc] init];
NSString *englishString = #"";
if(verse.subtitle.length>0)
{
NSMutableParagraphStyle *mutParaStyle=[[NSMutableParagraphStyle alloc] init];
[mutParaStyle setAlignment:NSTextAlignmentCenter];
englishSubtitleAttributedString = [[NSMutableAttributedString alloc] initWithString:verse.subtitle];
[englishSubtitleAttributedString addAttributes:[NSDictionary dictionaryWithObject:mutParaStyle
forKey:NSParagraphStyleAttributeName]
range:NSMakeRange(0,[[englishSubtitleAttributedString string] length])];
[englishAttributedString appendAttributedString:englishSubtitleAttributedString];
[englishAttributedString addAttribute:NSFontAttributeName value:[UIFont fontWithName:#"Arial" size:30] range:NSRangeFromString(verse.subtitle)];
NSLog(#"text us %#", englishAttributedString);
}// englishString = [englishString stringByAppendingString:[NSString stringWithFormat:#"%#\n\n", verse.subtitle]];
[englishAttributedString appendAttributedString:englishVerseAttributedString];
englishString = [englishString stringByAppendingString:[NSString stringWithFormat:#"[%#:%#] %#\n", verse.whichSura.sura_no, verse.verse_no, verse.english_version]];
if(verse.footnote.length>0)
englishString = [englishString stringByAppendingString: [NSString stringWithFormat:#"\n%#\n", verse.footnote]];
englishString = [englishString stringByReplacingOccurrencesOfString:#"“" withString:#"\"" ];
englishString = [englishString stringByReplacingOccurrencesOfString:#"_" withString:#"\n" ];
cell.quranVerseEnglishTextView.attributedText = englishAttributedString;
[cell.quranVerseEnglishTextView autoResizeWithMaxWidth:MAX_TEXT_WIDTH];
cell.quranVerseEnglishTextView.backgroundColor = [UIColor clearColor];
//English Content starts
//Arabic Content
CATextLayer *arabicTextLayer = [self customCATextLayer:verse.arabic_version];
[cell.arabicView.layer addSublayer:arabicTextLayer];
return cell;
}
I was facing the same problem until I read up about NSAttributedStrings (made available in iOS 6) on this tutorial here.
The following code will solve your issue:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:info.text attributes:#{ NSFontAttributeName : [UIFont fontWithName:#"Scheherazade" size:32], NSLigatureAttributeName: #2}];
cell.textLabel.attributedText = attributedString;
Out of curiosity, would I be correct to say that you opted to use CoreText because of difficulties in rendering embedded arabic fonts? I ventured the guess because I was attempting to use a similar method as you have done in your code when faced with that exact problem for a Quran app that I'm currently developing. If this so then I can confirm that using NSAttributedString also solves the problem. If you notice in the code above I've also set the NSLigatureAttributeName to 2 which according to the official Apple Class Reference Documentation means 'all ligatures'. Just note that this is something that I'm currently testing and I have yet to see the effects of this but I know that ligatures is a common problem in the rendering of some arabic fonts on certain platforms.
While on the subject, another common problem you may be facing is the line-spacing of arabic text and the slight overlapping of multi-line text and I've found that NSAttributedString can also be a good solution when used together with NSParagraphStyle (Hooray again for NSAttributedString!). Simply modify the above code as below:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:info.text attributes:#{ NSFontAttributeName : [UIFont fontWithName:#"Scheherazade" size:32], NSLigatureAttributeName: #2}];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:20];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [info.text length])];
cell.textLabel.attributedText = attributedString;
Hope this helps you or anyone else out there!
EDIT - Adding this helpful post on Common Mistakes With Adding Custom Fonts to Your iOS App for reference as a "checklist" when adding custom fonts on iOS.
Actually fixed the issue myself by adding the following line in cellforRowAtIndexPath:
if (cell == nil)
{
cell = [[QuranVersesViewCell alloc] init];
.....
and also did all the initialization and setting only when the cell was nil. And MOST importantly tagged the view layer and set the text for only the matching tagged view...

How to insert grouping comma in NSString as typed?

A user enters a numerical string in a UILabel and the text is displayed as the user types.
NSString *input = [[sender titleLabel] text];
[display_ setText:[[display_ text] stringByAppendingString:input]];
This works fine and I format the display using NSNumberFormatter so that if 1000000 is entered it is converted to 1,000,000 upon tapping another button.
However, I'd like to get those grouping commas to be displayed as the user types. I can understand how to insert things into strings, but how to do it as the user types is not clear to me. Would this require a mutable string?
Maybe somehow monitor the string length and split it into groups of three and make and display a new string with the commas inserted? I could probably do that, but it is the "as it is typed" part that has me stymied.
Another thought is to append and display the string, then read the display into a new NSString and format it and display it again right away. So I tried that, and it almost works:
if (userIsEntering)
{
NSNumberFormatter *fmtr = [[NSNumberFormatter alloc] init];
[fmtr setNumberStyle:NSNumberFormatterDecimalStyle];
[fmtr setGroupingSeparator:#","];
[fmtr setDecimalSeparator:#"."];
NSString *out = [[display_ text] stringByAppendingString:digit];
NSNumber *num = [fmtr numberFromString:out];
NSString* formattedResult = [fmtr stringFromNumber:num];
[display_ setText: [NSString stringWithFormat:#"%#", formattedResult]];
[fmtr release];
}
And, along with the fact that the formatter is created and released with every digit entered, after 4 digits it returns null.
UPDATE: I figured out how to do it in a label (with some help from #Michael-Frederick). It uses an NSNotification.
This works perfectly for non-decimal numbers, but when I try to enter a decimal point it is ignored and removed. If I do not invoke this method, the decimal point is accepted and all works well.
Numeric entry is as follows (from a button):
NSString *digit = [[sender titleLabel] text];
if (userIsStillWorking_)
{
[display_ setText:[[display_ text] stringByAppendingString:digit]];
}
else
{
[display_ setText: digit];
userIsStillWorking_ = YES;
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateDisplay" object:nil];
And the updateDisplay method called by the notification is:
{
NSString *unformattedValue = [display_.text stringByReplacingOccurrencesOfString:
#"," withString:#""];
unformattedValue = [unformattedValue stringByReplacingOccurrencesOfString:
#"." withString:#""];
NSDecimalNumber *amount = [NSDecimalNumber decimalNumberWithString:unformattedValue];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setGroupingSeparator:#","];
[formatter setDecimalSeparator:#"."];
[display_ setText: [ formatter stringFromNumber:amount]];
[formatter release];
}
I've tried commenting out
unformattedValue = [unformattedValue stringByReplacingOccurrencesOfString:
#"." withString:#""];
but that makes no difference.
EDIT:
A user cannot type into a uilabel. You need to use either a uitextfield or a uitextview.
If you want to use a uitextfield, do something like this...
- (void) viewDidLoad {
[super viewDidLoad];
[textField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}
- (void) textFieldDidChange:(UITextField *)textField {
NSString *unformattedValue = [textField.text stringByReplacingOccurrencesOfString:#"," withString:#""];
unformattedValue = [unformattedValue stringByReplacingOccurrencesOfString:#"." withString:#""];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter setGroupingSeparator:#","];
[formatter setDecimalSeparator:#"."];
NSNumber *amount = [NSNumber numberWithInteger:[unformattedValue intValue]];
textField.text = [formatter stringFromNumber:amount];
[formatter release];
}
Note that you are correct that NSNumberFormatter should be declared outside of the textFieldDidChange method. Note that this code would actually be for an integer. You could have to switch intValue to floatValue if need be. This code is untested, it is more of a general guide.
The best way to do this is to use 2 UIlabels. 1 of the labels is used to feed your NSNumberFormatter object by using [NSString stringByAppendingString:digit]; The other label is actually displayed. The trick is to set the label that is unformatted to hidden and the other label is set as an output for the number formatter. By feeding the hidden label to the number formatter, and outputting the displayed label from the number formatter, the number formatter should be set to the NSDecimalNumber style. Setting it all up this way, the result displayed is automatic commas while typing.

Resources