NSPasteboard readObjectsForClasses:options: returns value twice - macos

Right to the point, then:
First snippet (AppDelegate):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
//...code taken out...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *incomingEvent) {
if ([incomingEvent type] == NSKeyDown) {
NSUInteger flags = [incomingEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
if (flags==NSCommandKeyMask && ([incomingEvent keyCode] == 8)) {
[ClipboardUtilities logger:#"cmd+c recognized"];
[self determineAndAddToHistory];
}
}
}];
}
Second snippet (AppDelegate):
-(void) determineAndAddToHistory {
id clipDat = [ClipboardUtilities getClipboardDataNatively];
if ([clipDat isKindOfClass:[NSAttributedString class]])
NSLog(#"clipDat.string = %#",((NSAttributedString*)clipDat).string);
}
Third snippet (ClipboardUtilities class):
+(id) getClipboardDataNatively {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *classArray = #[[NSAttributedString class], [NSImage class]];
NSDictionary *options = [NSDictionary dictionary];
NSArray *objectsToPaste = nil;
BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
if (ok) {
objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
}
NSLog(#"objectsToPaste count = %li",[objectsToPaste count]);
return [objectsToPaste objectAtIndex:0];
}
I've noticed some strange behaviour that I will try to explain with an example:
Input
Cmd+C the string "A"
Cmd+C the string "B"
Cmd+C the string "C"
Cmd+C the string "D"
Output from determineAndAddToHistory
A
A
B
C
So I'm noticing that it retains that first item for some reason...then returns me the second most recent item each time. I've tried outputting the objectsToPaste array in the getClipboardDataNatively method, and this still is the case. Could someone please let me know how I would approach this problem, or how they've solved it?
P.S. my ClipboardUtilities class does not implement any Delegates, or inherit from anything but NSObject.

Well I guess since nobody likes long questions (I'll have to figure out how to shorten this), I figured something out. For some reason, I get the hotkey call really quickly (the clipboard gets updated AFTER the key is called in fact). As a result, I just have a small delay, and my model is now updated properly:
NSTimer* briefDelay = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(determineAndAddToHistory) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:briefDelay forMode:NSRunLoopCommonModes];
I do not invalidate the timer manually, as per the documentation:
repeats
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.

Related

OSX - cannot set an NSTextField with a string from an NSTimer

I cannot seem to set a timer output to an NSTextField. I've looked at this Q/A, among many, but must be doing something different but wrong.
I initialize the timer and the textfield in WindowDidLoad.
My timer method is as follows:
time += 1;
if((time % 60) <= 9) {
NSString *tempString = [NSString stringWithFormat:#"%d:0%d",(time/60),(time%60)];
[timerTextField setStringValue:tempString];
} else {
NSString *tempString = [NSString stringWithFormat:#"%d:%d",(time/60),(time%60)];
timerTextField.stringValue = tempString;
}
NSLog(#"timerTextField: %#", timerTextField.stringValue);
As you can see, I log the textfield output.
I have an IBOutlet connection from the the File's owner to the timerTextField.
I can see the output correctly in the log, but the textfield string is not populated.
Can anyone see anything I'm doing wrong? Thanks
The code you posted looks OK to me. (You could replace it by binding your timerTextField to the time value via Cocoa Bindings though).
How did you create your timer and to which runloop(mode) did you add it?
Another problem could be that the outlet to your timerTextField is not connected. Can you set a breakpoint in your timer selector and check if timerTextField is not nil.
Update:
I just tried your code and for me it works.
Given that you have an IBOutlet named timerTextField connected to a NSTextField instance in Interface Builder, this code updates the textfield each second:
#interface SSWAppDelegate ()
{
NSUInteger time;
}
#property (weak) IBOutlet NSTextField *timerTextField;
#end
#implementation SSWAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateTime:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)updateTime:(id)sender
{
time += 1;
if((time % 60) <= 9) {
NSString *tempString = [NSString stringWithFormat:#"%lu:0%lu",(time/60),(time%60)];
[self.timerTextField setStringValue:tempString];
} else {
NSString *tempString = [NSString stringWithFormat:#"%lu:%lu",(time/60),(time%60)];
self.timerTextField.stringValue = tempString;
}
NSLog(#"timerTextField: %#", self.timerTextField.stringValue);
}
#end

Memory leaks in ipod - 100% leak while using instruments and iPod

-(NSString*)readTextColumnWithIndex:(int)index {
char* val = (char *)sqlite3_column_text(statement,index);
if (val==NULL) {
return nil;
}
return [NSString stringWithUTF8String:val];
}
I have 100% leak at return [NSString stringWithUTF8String:val];. Not sure how to fix them. I believe only alloc, retain etc could end up in memory leaks. Any help is greatly appreciated.
[NSString stringWithUTF8String:val] is the statement that created the object that is leaking - you need to see what happens to it next.
Are you retaining the NSString returned by readTextColumnWithIndex: somewhere?
-(NSDictionary*)loadDataRow:(SqlQuery*)q {
return [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithFormat:#"%d",[q readIntColumn]],
#"TOCID",[q readTextColumn],#"SubChapter_Name",[q readTextColumn],
#"HtmlFileName",[q readTextColumn],#"ImageName",[q readTextColumn],
#"RefsTitleSection",[NSString stringWithFormat:#"%d",
[q readIntColumn]],#"PageId",
nil];
}
This is where it is called. readIntColumn in turn calls readTextColumnWithIndex and NSDictionary is returned back to an the caller method
chap_Arr = nil;
while([query step]) {
ChapName = [query readTextColumn];
chap_Arr = [mData objectForKey:ChapName];
if (!chap_Arr) {
chap_Arr = [[[NSMutableArray alloc] init] autorelease];
[mData setObject:chap_Arr forKey:ChapName];
}
[chap_Arr addObject:[self loadDataRow:query]];
}
I am not sure how i can release the chap_Arr as it is in a loop. I do have an RELEASE_SAFELY in dealloc.

Crash when trying to get NSManagedObject from NSFetchedResultsController after 25 objects?

I'm relatively new to Core Data on iOS, but I think I've been getting better with it. I've been experiencing a bizarre crash, however, in one of my applications and have not been able to figure it out.
I have approximately 40 objects in Core Data, presented in a UITableView. When tapping on a cell, a UIActionSheet appears, presenting the user with a UIActionSheet with options related to the cell that was selected. So that I can reference the selected object, I declare an NSIndexPath in my header called "lastSelection" and do the following when the UIActionSheet is presented:
// Each cell has a tag based on its row number (i.e. first row has tag 0)
lastSelection = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:lastSelection];
BOOL onDuty = [[managedObject valueForKey:#"onDuty"] boolValue];
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:#"Status" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
if(onDuty) {
[actionSheet addButtonWithTitle:#"Off Duty"];
} else {
[actionSheet addButtonWithTitle:#"On Duty"];
}
actionSheet.actionSheetStyle = UIActionSheetStyleBlackOpaque;
// Override the typical UIActionSheet behavior by presenting it overlapping the sender's frame. This makes it more clear which cell is selected.
CGRect senderFrame = [sender frame];
CGPoint point = CGPointMake(senderFrame.origin.x + (senderFrame.size.width / 2), senderFrame.origin.y + (senderFrame.size.height / 2));
CGRect popoverRect = CGRectMake(point.x, point.y, 1, 1);
[actionSheet showFromRect:popoverRect inView:[sender superview] animated:NO];
[actionSheet release];
When the UIActionSheet is dismissed with a button, the following code is called:
- (void)actionSheet:(UIActionSheet *)actionSheet willDismissWithButtonIndex:(NSInteger)buttonIndex {
// Set status based on UIActionSheet button pressed
if(buttonIndex == -1) {
return;
}
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:lastSelection];
if([actionSheet.title isEqualToString:#"Status"]) {
if([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:#"On Duty"]) {
[managedObject setValue:[NSNumber numberWithBool:YES] forKey:#"onDuty"];
[managedObject setValue:#"onDuty" forKey:#"status"];
} else {
[managedObject setValue:[NSNumber numberWithBool:NO] forKey:#"onDuty"];
[managedObject setValue:#"offDuty" forKey:#"status"];
}
}
NSError *error;
[self.managedObjectContext save:&error];
[tableView reloadData];
}
This might not be the most efficient code (sorry, I'm new!), but it does work. That is, for the first 25 items in the list. Selecting the 26th item or beyond, the UIActionSheet will appear, but if it is dismissed with a button, I get a variety of errors, including any one of the following:
[__NSCFArray section]: unrecognized selector sent to instance 0x4c6bf90
Program received signal: “EXC_BAD_ACCESS”
[_NSObjectID_48_0 section]: unrecognized selector sent to instance 0x4c54710
[__NSArrayM section]: unrecognized selector sent to instance 0x4c619a0
[NSComparisonPredicate section]: unrecognized selector sent to instance 0x6088790
[NSKeyPathExpression section]: unrecognized selector sent to instance 0x4c18950
If I comment out NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:lastSelection]; it doesn't crash anymore, so I believe it has something do do with that. Can anyone offer any insight? Please let me know if I need to include any other information. Thanks!
EDIT: Interestingly, my fetchedResultsController code returns a different object every time. Is this expected, or could this be a cause of my issue? The code looks like this:
- (NSFetchedResultsController *)fetchedResultsController {
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Employee" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:80];
// Edit the sort key as appropriate.
NSString *sortKey;
BOOL ascending;
if(sortControl.selectedSegmentIndex == 0) {
sortKey = #"startTime";
ascending = YES;
} else if(sortControl.selectedSegmentIndex == 1) {
sortKey = #"name";
ascending = YES;
} else {
sortKey = #"onDuty";
ascending = NO;
}
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:ascending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
NSError *error = nil;
if (![fetchedResultsController_ performFetch:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
//NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return fetchedResultsController_;
}
This happens when I set a breakpoint:
(gdb) po [self fetchedResultsController]
<NSFetchedResultsController: 0x61567c0>
(gdb) po [self fetchedResultsController]
<NSFetchedResultsController: 0x4c83630>
It's prob the case that self.fetchedResultsController is pointing to the wrong memory location. You will need to check if the object has been retained.
Figured it out! Looks like it was an issue with autoreleased objects.
When I turned on NSZombieEnabled, I got this:
*** -[NSIndexPath section]: message sent to deallocated instance 0xa674530
I simply changed lastSelection = [NSIndexPath indexPathForRow:[sender tag] inSection:0]; to lastSelection = [[NSIndexPath indexPathForRow:[sender tag] inSection:0] retain]; and that took care of it.

Memory problems with NSMutableDictionary, causing NSCFDictionary memory leaks

Help me please with the following problem:
- (NSDictionary *)getGamesList
{
NSMutableDictionary *gamesDictionary = [[NSMutableDictionary dictionary] retain];
// I was trying to change this on the commented code below, but did have no effect
// NSMutableDictionary *gamesDictionary = [[NSMutableDictionary alloc] init];
// [gamesDictionary retain];
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *key = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSArray *gameDate = [key componentsSeparatedByString:#" "];
NSNumber *_id = [[NSNumber alloc] initWithInt:sqlite3_column_int(statement, 0)];
NSString *date_time = [NSString stringWithFormat:#"%#, %#",[gameDate objectAtIndex:0],[gameDate objectAtIndex:2]];
if (![gamesDictionary valueForKey:date_time]) [gamesDictionary setValue:[NSMutableArray array] forKey:date_time];
[[gamesDictionary valueForKey:date_time] addObject:[[_id copy] autorelease]];
[_id release];
}
sqlite3_reset(statement);
return gamesDictionary;
}
The leak starts in another method of another class, there the getGamesList method is called, like this:
NSMutableDictionary *gamesDictionary;
gamesDictionary = [[NSMutableDictionary dictionaryWithDictionary:[appDelegate getGamesList]] retain];
After that there are a lot of leaks that points to NSCFArray in the string:
NSArray *keys = [[NSArray arrayWithArray:[gamesDictionary allKeys]] retain];
in this method:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *keys = [[NSArray arrayWithArray:[gamesDictionary allKeys]] retain];
if ([keys count] != 0) return [[keys objectAtIndex:section] uppercaseString];
return #"";
}
I assume these things are connected to each other, but I still can not understand all of the memory management tips.
Thanks a lot!
Didn't use Cocoa for years (that's why I can't tell you an exact answer :/). But I guess your problem is that you systematically use retain on your objects.
Since the object reference count never get to 0, all dictionaries are keep in memory and not freed.
Try to remove the retain on [NSArray arrayWithArray] and [NSMutableDictionary dictionaryWithDictionary
http://en.wikibooks.org/wiki/Programming_Mac_OS_X_with_Cocoa_for_beginners/Some_Cocoa_essential_principles#Retain_and_Release
It does look like you are over-retaining your array.
When you create the gamesDictionary it is created with an retain count of +1. You then retain it (count becomes +2). When you get the value outside this function you retain again (count becomes +3).
You are correct that if you create an object you are responsible for it's memory management. Also, when you get an object from a method, you should retain it if you want to keep it around for longer than the span of the function. In your case, you just want to get at some of the properties of the object, so you don't need to retain it.
Here is a suggestion:
- (NSDictionary *)getGamesList
{
NSMutableDictionary *gamesDictionary = [NSMutableDictionary dictionary]; // Remove the retain.
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *key = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSArray *gameDate = [key componentsSeparatedByString:#" "];
NSNumber *_id = [[NSNumber alloc] initWithInt:sqlite3_column_int(statement, 0)];
NSString *date_time = [NSString stringWithFormat:#"%#, %#",[gameDate objectAtIndex:0],[gameDate objectAtIndex:2]];
if (![gamesDictionary valueForKey:date_time]) [gamesDictionary setValue:[NSMutableArray array] forKey:date_time];
[[gamesDictionary valueForKey:date_time] addObject:[[_id copy] autorelease]];
[_id release];
}
sqlite3_reset(statement);
return gamesDictionary;
}
This next bit is messy. you create a new dictionary and retain it. The original dictionary is not autoreleased, so the count isn't decremented and it always hangs around. Just assign the dictionary rather than create a new one.
NSMutableDictionary *gamesDictionary = [[appDelegate getGamesList] retain];
// Retaining it, becuase it looks like it's used elsewhere.
Now, in this method:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *returnString;
// Don't need to retain the keys because you are only using it within the function
// and since you didn't alloc, copy or retain the array it contains, you aren't responsible for it's memory management.
NSArray *keys = [NSArray arrayWithArray:[gamesDictionary allKeys]];
if ([keys count] != 0) {
returnString = [[NSString alloc] initWithString:[[keys objectAtIndex:section] uppercaseString]];
return [returnString autorelease];
}
return #"";
}

Struggling with currency in Cocoa

I'm trying to do something I'd think would be fairly simple: Let a user input a dollar amount, store that amount in an NSNumber (NSDecimalNumber?), then display that amount formatted as currency again at some later time.
My trouble is not so much with the setNumberStyle:NSNumberFormatterCurrencyStyle and displaying floats as currency. The trouble is more with how said numberFormatter works with this UITextField. I can find few examples. This thread from November and this one give me some ideas but leaves me with more questions.
I am using the UIKeyboardTypeNumberPad keyboard and understand that I should probably show $0.00 (or whatever local currency format is) in the field upon display then as a user enters numerals to shift the decimal place along:
Begin with display $0.00
Tap 2 key: display $0.02
Tap 5 key: display $0.25
Tap 4 key: display $2.54
Tap 3 key: display $25.43
Then [numberFormatter numberFromString:textField.text] should give me a value I can store in my NSNumber variable.
Sadly I'm still struggling: Is this really the best/easiest way? If so then maybe someone can help me with the implementation? I feel UITextField may need a delegate responding to every keypress but not sure what, where and how to implement it?! Any sample code? I'd greatly appreciate it! I've searched high and low...
Edit1: So I'm looking into NSFormatter's stringForObjectValue: and the closest thing I can find to what benzado recommends: UITextViewTextDidChangeNotification. Having really tough time finding sample code on either of them...so let me know if you know where to look?
My solution:
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
// Clear all characters that are not numbers
// (like currency symbols or dividers)
NSString *cleanCentString = [[textField.text
componentsSeparatedByCharactersInSet:
[[NSCharacterSet decimalDigitCharacterSet] invertedSet]]
componentsJoinedByString:#""];
// Parse final integer value
NSInteger centAmount = cleanCentString.integerValue;
// Check the user input
if (string.length > 0)
{
// Digit added
centAmount = centAmount * 10 + string.integerValue;
}
else
{
// Digit deleted
centAmount = centAmount / 10;
}
// Update call amount value
[_amount release];
_amount = [[NSNumber alloc] initWithFloat:(float)centAmount / 100.0f];
// Write amount with currency symbols to the textfield
NSNumberFormatter *_currencyFormatter = [[NSNumberFormatter alloc] init];
[_currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[_currencyFormatter setCurrencyCode:#"USD"];
[_currencyFormatter setNegativeFormat:#"-¤#,##0.00"];
textField.text = [_currencyFormatter stringFromNumber:_amount];
[_currencyFormatter release]
// Since we already wrote our changes to the textfield
// we don't want to change the textfield again
return NO;
}
Here's the rough plan of attack I'd use if I had to write that now. The trick will be typing into a hidden UITextField and updating a UILabel with the formatted value as the user types.
Create a UITextField, make it hidden, assign it a delegate, and then make it the first responder to summon the keyboard.
In your delegate, respond to the textDidChange: message (too lazy to look up the exact name) by taking the text field's new value and converting it to a number. Make sure empty string converts to zero.
Run the number through your formatter, and update a UILabel with that formatted currency value.
On every key press, the label will be updated, so the user will feel as though she is editing the formatted value, when she is really editing the hidden text field. How sneaky!
Well, i think this is better:
- (void)viewWillDisappear:(BOOL)animated{ [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil]; }
-(void)viewDidAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textDidChanged:) name:UITextFieldTextDidChangeNotification object:nil];
}
-(void)textDidChanged:(NSNotification *)notification{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
UITextField * textField= [notification object];
NSString * sinPesos= [textField.text stringByReplacingOccurrencesOfString:#"$" withString:#""];
NSString * sinPuntos= [sinPesos stringByReplacingOccurrencesOfString:#"." withString:#""];
float monto = [sinPuntos floatValue];
monto= monto/100;
NSString * cardText= [[self montoFormatter] stringFromNumber:[NSNumber numberWithDouble:monto]];
textField.text = ([cardText isEqualToString: #"0"] ? #"":cardText);
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textDidChanged:) name:UITextFieldTextDidChangeNotification object:nil];
}
-(NSNumberFormatter *)montoFormatter{
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setMaximumFractionDigits:2];
return [numberFormatter autorelease];
}
Try it out :)
Since I am lazy, and I can't stand having my input reformatted "to help me out" on the fly, I say:
Just let them enter a decimal number. When they leave the field, reformat it.
Well, this is a bit late but I figured I would give this a shot anyway. This is probably more of a workaround and may be messy code, but it worked for me. I connected a label and a hidden textfield in IB, and I wrote this code using the UITextFieldDelegate delegate.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField == fineHiddenTextField) {
NSString *fineText = [textField.text stringByReplacingCharactersInRange:range withString:string];
if ([fineText length] == 0) {
NSString *emptyFine = #"0.00";
float fineValue = [emptyFine floatValue];
fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
} else if ([fineText length] == 1) {
NSString *firstDec = [NSString stringWithFormat:#"0.0%#", fineText];
float fineValue = [firstDec floatValue];
fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
} else if ([fineText length] == 2) {
NSString *secondDec = [NSString stringWithFormat:#"0.%#", fineText];
float fineValue = [secondDec floatValue];
fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
} else {
int decimalPlace = [fineText length] - 2;
NSString *fineDollarAmt = [fineText substringToIndex:decimalPlace];
NSString *fineCentsAmt = [fineText substringFromIndex:decimalPlace];
NSString *finalFineValue = [NSString stringWithFormat:#"%#.%#", fineDollarAmt, fineCentsAmt];
float fineValue = [finalFineValue floatValue];
fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
}
//fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
return YES;
}
}
Not exactly the neatest, but it really got the job done. The initial if statement was just to make sure that this was only happening for this one particular textField (As there are multiple throughout the same page.) This code was intended for the input of money (The amount of a fine paid) and I wanted it to be simple and easy to use. Just set your label to align from the right, and it should.
I am a little wired on coffee right now, but I will answer any questions you may have. :)
Here's my solution!
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
float valorUnit = [textField.text floatValue];
if( string.length > 0)
{
float incremento = [string floatValue];
valorUnit = valorUnit * 10.f + (incremento/100.f);
NSString* strVal = [NSString stringWithFormat:#"%f", valorUnit];
if (valorUnit > 0.f && valorUnit < 10.f) {
textField.text = [strVal substringToIndex:3];
}
else if (valorUnit < 100.f && valorUnit >= 10.f)
{
textField.text = [strVal substringToIndex:4];
}
else if (valorUnit >=100.f && valorUnit <1000.f)
{
textField.text = [strVal substringToIndex:5];
}
else {
return NO;
}
}
else {
valorUnit = (valorUnit/10.f);
NSString* strVal = [NSString stringWithFormat:#"%f", valorUnit];
if (valorUnit > 0.f && valorUnit < 10.f) {
textField.text = [strVal substringToIndex:5];
}
else if (valorUnit < 100.f && valorUnit >= 10.f)
{
textField.text = [strVal substringToIndex:6];
}
else if (valorUnit >=100.f && valorUnit <1000.f)
{
textField.text = [strVal substringToIndex:7];
}
else {
return NO;
}
}
return YES;
}
I wrote an open source UITextField subclass to handle this, available here:
https://github.com/TomSwift/TSCurrencyTextField
The approach I took is similar to what Lars Schneider suggests in his popular answer. But my version is fully encapsulated in a reusable component that you use anywhere, just like UITextField. If you choose to implement any UITextFieldDelegate methods you can, but this is not required.

Resources