Resize UITextField Based On Text Entered - objective-c

I have UITextfield that is set to show only 1 character upon initialization, however I use constraints to make the field be able to expand as needed.
My constraint:
#"H:[txtField(<=80)]-14-|
When I set the textField via the text attribute it resizes perfectly, however when I try and enter it in via the keyboard the best I can get is to use sizeToFit inside the shouldChangeCharactersInRange...which kind of works but instead of the dollar amount moving inward to the left on the iphone it moves outward to the right and off the screen.
How would I make it move inward to the left?

I ended up using this little bit I found on StackOverflow in the
shouldChangeCharactersInRange
double currentValue;
NSString *str;
if(![textField.text length]){
str = #"$ 0.00";
}else{
str = textField.text;
}
currentValue = [[str substringFromIndex:1] doubleValue];
double cents = round(currentValue * 100.0f);
if ([string length]) {
for (size_t i = 0; i < [string length]; i++) {
unichar c = [string characterAtIndex:i];
if (isnumber(c)) {
cents *= 10;
cents += c - '0';
}
}
} else {
// back Space
cents = floor(cents / 10);
}
textField.text = [NSString stringWithFormat:#"%.2f", cents / 100.0f];
//Add this line
[textField setText:[NSString stringWithFormat:#"$%#",[textField text]]];
return NO;

Related

Keyboard get automatically dismissed on iOS 11.2.6

When trying to type in a textfield after first character keyboard automatically gets dismissed, I have not implemented any textfield delegates, except shouldChangeCharacterInRange and in this method there is no resignFirstResponder or endEditing and there is not disabled textFields.
if ([theTextField isEqual:_userName]) {
MAX_DIGITS = 15;
NSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ0123456789-._"];
for (int i = 0; i < [string length]; i++) {
unichar c = [string characterAtIndex:i];
if (![myCharSet characterIsMember:c]) {
return NO;
} else {
return [theTextField.text stringByReplacingCharactersInRange:range withString:string].length <= MAX_DIGITS;
}
}
}
retutn YES:

Limit text length of auto-scrolling NSTextView

I have a NSTextView that is displaying what I would call a "rolling log". New AttributedString's are being added just about every second. What I would like to do is truncate from the beginning of the NSTextView if the string has hit a certain length, or a certain number of lines. This is so that the displayed log doesn't take up a ton of memory.
How should I best go about this? I have some code though it doesn't appear to be working as I would expect, specifically around the auto scrolling.
Expected behavior:
Remove leading lines if needed (I don't really care if this is lines or number of characters, whichever is easiest).
Auto-scroll to the bottom if the view isn't scrolled up (so if the user has currently scrolled up, they're not auto-scrolled to the bottom).
The code:
- (void)append:(TextTag*)text toTextView:(MyNSTextView *) textView {
dispatch_async(dispatch_get_main_queue(), ^{
NSAttributedString *attr = [self stringFromTag:text];
NSScroller *scroller = [[textView enclosingScrollView] verticalScroller];
double autoScrollToleranceLineCount = 3.0;
NSUInteger lines = [self countLines:[textView string]];
double scrolled = [scroller doubleValue];
double scrollDiff = 1.0 - scrolled;
double percentScrolled = autoScrollToleranceLineCount / lines;
BOOL shouldScrollToBottom = scrollDiff <= percentScrolled;
[textView.textStorage beginEditing];
if (lines >= 10000) {
NSRange removeRange = [self getRemovalRange:textView.string];
[textView.textStorage deleteCharactersInRange:removeRange];
}
[[textView textStorage] appendAttributedString:attr];
[textView.textStorage endEditing];
if(shouldScrollToBottom) {
[textView scrollRangeToVisible:NSMakeRange([[textView string] length], 0)];
}
});
}
- (NSRange)getRemovalRange:(NSString *)s {
NSUInteger numberOfLines, index, stringLength = [s length];
for (index = 0, numberOfLines = 0; index < stringLength;
numberOfLines++) {
index = NSMaxRange([s lineRangeForRange:NSMakeRange(index, 0)]);
if (numberOfLines >= 100) {
break;
}
}
return NSMakeRange(0, index);
}
- (NSUInteger) countLines:(NSString *)s {
NSUInteger numberOfLines, index, stringLength = [s length];
for (index = 0, numberOfLines = 0; index < stringLength;
numberOfLines++) {
index = NSMaxRange([s lineRangeForRange:NSMakeRange(index, 0)]);
}
return numberOfLines;
}
Here's what I did (years ago, trial and error).
- (void)scrollProgressTextViewToEnd
{
if ([progressTextView isFlipped])
[progressTextView scrollPoint:NSMakePoint(0.0, NSMaxY([progressTextView frame]) - NSHeight([progressTextView visibleRect]))];
else
[progressTextView scrollPoint:NSMakePoint(0.0, 0.0)];
}
- (void)appendToProgressText:(NSString *)theString bold:(BOOL)theBold
{
[progressTextView.textStorage beginEditing];
[self appendToProgressText:theString bold:theBold];
[progressTextView.textStorage endEditing];
[progressTextView didChangeText];
[self performSelector:#selector(scrollProgressTextViewToEnd) withObject:nil afterDelay:0];
}
Method appendToProgressText adds theString to progressTextView.textStorage and doesn't use progressTextView.

Obj C - Word Line Truncation

I want to show my long text in a UILabel. But, My design having small size of frame for that UILabel. So, i want to truncate my long text like this[see below]:
Ex:
UILabel Text: "I want to show my long text in a UILabel"
Recent Result: [Using lineBreakMode:]
I want to s........a UILabel
I want to s.....
I want to s
Expected Result: "I want to...."
[Note:  I want truncation after the word which can fit within their label frame.]
I hope that you can sense about my expected result. Sorry for my English!.
I am not sure whether there is API for this. If you are not getting answers. You can use the below logic to achieve This is not optimum logic.
-(NSString *) textThatFits:(NSString *) originalText font:(UIFont *) font
{
NSArray *array = [originalText componentsSeparatedByString:#" "];
NSString *stringThatFits;
for (int i = 0 ; i < [array count]; i++)
{
NSString *tempString = [stringThatFits stringByAppendingFormat:#" %#", array[i]];
CGRect boundingRect = [tempString boundingRectWithSize:CGSizeMake(999, 999)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil];
if (boundingRect.size.width < self.yourLabel.width) {
return stringThatFits;
}
else
{
stringThatFits = tempString;
}
}
return stringThatFits;
}
According to the OP excepted result and the #Naveen logic, I develop the code which works but with some restriction.
Restriction:
Sometimes, extending that label width by adding 10.0 value.
don't give any spaces at begin and end of label text.
Design:
Controls: A UIButton, UITextField, UILabel
Type your text in the UITextField.
Do Action to display your excepted result in the UILabel.
Code:
-(IBAction)actionDisplayTextWithTruncate:(id)sender{
lblFinalResult.frame=CGRectMake(60, 345, 55, 21);
NSString *strGivenText, *strFuncResult, *stringThatFits;
int spaceCount;
//Custom Truncate Function
strGivenText=txtFldGivenText.text;
arrForGivenText_Words = [strGivenText componentsSeparatedByString:#" "];
stringThatFits=#"";
strFuncResult=#"";
for (int i = 0 ; i < [arrForGivenText_Words count]; i++)
{
/* must follow #" %#" - a space before %# */
NSString *tempString = [stringThatFits stringByAppendingFormat:#" %#", arrForGivenText_Words[i]];
CGRect boundingRect = [tempString boundingRectWithSize:CGSizeMake(999, 999) options:NSStringDrawingTruncatesLastVisibleLine attributes:#{NSFontAttributeName:lblFinalResult.font} context:nil];
if (boundingRect.size.width > lblFinalResult.frame.size.width) //Breakpoint1
{
if(i==0){
[lblFinalResult setText:#"..."];
return;
}
else{
for (int j = 0 ; j < i; j++)
{
strFuncResult = [strFuncResult stringByAppendingFormat:#"%# ",arrForGivenText_Words[j]];
NSLog(#"Present_a1: %#", strFuncResult);
}
strFuncResult = [strFuncResult substringToIndex:strFuncResult.length-(strFuncResult.length>0)];
lblFinalResult.frame= CGRectMake(lblFinalResult.frame.origin.x, lblFinalResult.frame.origin.y, lblFinalResult.frame.size.width+10, lblFinalResult.frame.size.height);
strFuncResult=[strFuncResult stringByAppendingString:#"..."];
[lblFinalResult setText:strFuncResult];
return;
}
}
else{
stringThatFits = tempString;
NSLog(#"Present_a2: %#", stringThatFits);
}
}
[lblFinalResult setText:stringThatFits];
}

How to restrict number of fraction digits when parsing number from string?

I want to restrict the number of fraction digits a user is allowed to enter into a UITextField that only accepts (localized) numeric input.
Example with 4 fraction digits allowed:
Good: 42, 10.123, 12345.2345
Bad: 0.123456, 6.54321
Right now, I'm using NSNumberFormatter's numberFromString: in the UITextField delegate's textField:shouldChangeCharactersInRange:replacementString: to determine whether it's a legal numeric value.
Unfortunately, NSNumberFormatter seems to ignore maximumFractionDigits in numberFromString:. In tests using getObjectValue:forString:range:error: I had the same problem, and range also was the full length of the string afterwards (unless I start entering letters; then range indicates only the part of the string with digits):
NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
formatter.maximumFractionDigits = 3;
formatter.roundingMode = NSNumberFormatterRoundHalfUp;
formatter.generatesDecimalNumbers = YES;
NSDecimalNumber* n = (NSDecimalNumber*)[formatter numberFromString:#"10.12345"];
NSLog(#"Number: %#", n.description); // expected: 10.123, but is: 10.12345
How to best restrict the number of fraction digits in user input?
after you get the unrestricted number, you can use stringWithFormat on that number to create a string with a certain number of decimal places.
eg.
double number = myTextField.text.doubleValue;
NSString *restrictedString = [NSString stringWithFormat:#"%.4f", number];
There are a few ways to do this, but the easiest is probably to split the string into two parts (you will have to localize the '.') and check the length of the second part, like this:
- (BOOL)LNNumberIsValid:(NSString *)string
{
NSArray *numArray = [string componentsSeparatedByString:#"."];
if ([numArray count] == 2)
if ([[numArray objectAtIndex:1] length] > 4)
return NO;
return YES;
}
// Tests
NSLog(#"42: %i", [self LNNumberIsValid:#"42"]); // 1
NSLog(#"10.123: %i", [self LNNumberIsValid:#"10.123"]); // 1
NSLog(#"12345.2345: %i", [self LNNumberIsValid:#"12345.2345"]); // 1
NSLog(#"0.123456: %i", [self LNNumberIsValid:#"0.123456"]); // 0
NSLog(#"6.54321: %i", [self LNNumberIsValid:#"6.54321"]); // 0
EDIT:
The problem with the code that you added to your question is that you are printing the description of the NSDecimalNumber, which is not localized or limited to the number of digits. The NSDecimalNumber itself stores everything that you give it, so you need to change the original string (like my example above) if you want to change that. However, once you have your NSDecimalNumber, you can use the same number formatter to convert it back to a string in the format that you like:
NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
formatter.maximumFractionDigits = 3;
formatter.roundingMode = NSNumberFormatterRoundHalfUp;
formatter.generatesDecimalNumbers = YES;
NSDecimalNumber* n = (NSDecimalNumber*)[formatter numberFromString:#"10.12345"];
NSString *s = [formatter stringFromNumber:n];
NSLog(#"Number: %#", s); // expected: 10.123, and is: 10.123
The way I solved this is by checking the position of the decimal separator and making sure that the insertion either is before that position or the insertion would not exceed the maximum number of fraction digits.
Also, I check that the input of a new separator does not occur at a place that would lead to more then the allowed fraction digits and that not more than one separators can be inserted
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *separator = self.numberFormatter.decimalSeparator;
if(string.length == 0) {
// Empty String means deletion, always possible
return YES;
}
// Check for valid characters (0123456789 + decimal Separator)
for (int i = 0; i < [string length]; ++i) {
unichar c = [string characterAtIndex:i];
if (![self.legalCharSet characterIsMember:c])
{
return NO;
}
}
// Checks if input is separator
if([string isEqualToString:separator]) {
// Check that separator insertion would not lead to more than 2 fraction digits and that not more than one separators are inserted
// (the MIN() makes sure that length - kMaxFractionDigits won’t be below 0 as length and location are NSUIntegers)
return range.location >= self.valueField.text.length - MIN(self.valueField.text.length,kMaxFractionDigits) && [self.valueField.text containsString:separator] == NO;
} else {
// Check if a separator is already included in the string
NSRange separatorPos = [self.valueField.text rangeOfString: separator];
if(separatorPos.location != NSNotFound) {
// Make sure that either the input is before the decimal separator or that the fraction digits would not exceed the maximum fraction digits.
NSInteger fractionDigits = self.valueField.text.length - (separatorPos.location + 1);
return fractionDigits + string.length <= kMaxFractionDigits || range.location <= separatorPos.location;
}
}
return YES;
}
The method may not be bullet proof but it should be sufficient for common text insertions.

How to know if NSString fits in UILabel or not and index of the last string which fits?

I have 4 lines UILabel with exact frame and font.
I need to know if this string fits the label and what is the index of the last character which fits.
The kernel of the answer is in Cupcake's referenced posting. Anyway, you can use sizeWithFont:constrainedToSize:lineBreakMode: to figure out what the size of a frame would be with a particular font in a label of a given width given a specific word wrapping, e.g.
CGSize size = [string sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:UILineBreakModeWordWrap];
Set sizeConstraint to be the same width of your label, but set the height to be larger. If the resulting size.height is larger than your UILabel, then your string is too long. Theoretically, you could remove the last character/word and try again and repeat until it fits.
If you think the strings might be very long, you might want to go the other way, start with a short portion of the string and keep adding characters until it's too large, and then you know the last character.
Either way, this iterative calculation of the size can be pretty cpu intensive operation, so be careful.
Update:
Here is an algorithm that returns the length of NSString that can fit into the UILabel in question using the default font (but ignoring minimum font size):
- (NSUInteger)fitString:(NSString *)string intoLabel:(UILabel *)label
{
UIFont *font = label.font;
UILineBreakMode mode = label.lineBreakMode;
CGFloat labelWidth = label.frame.size.width;
CGFloat labelHeight = label.frame.size.height;
CGSize sizeConstraint = CGSizeMake(labelWidth, CGFLOAT_MAX);
if ([string sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:mode].height > labelHeight)
{
NSString *adjustedString;
for (NSUInteger i = 1; i < [string length]; i++)
{
adjustedString = [string substringToIndex:i];
if ([adjustedString sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:mode].height > labelHeight)
return i - 1;
}
}
return [string length];
}
You could probably make this more efficient if you, for example, checked if word break mode, jumping to the next word separator and then calling sizeWithFont, but for small UILabels this might be sufficient. If you wanted to leverage word-wrap logic to minimize the number of times you call sizeWithFont, you might have something like:
- (NSUInteger)fitString:(NSString *)string intoLabel:(UILabel *)label
{
UIFont *font = label.font;
UILineBreakMode mode = label.lineBreakMode;
CGFloat labelWidth = label.frame.size.width;
CGFloat labelHeight = label.frame.size.height;
CGSize sizeConstraint = CGSizeMake(labelWidth, CGFLOAT_MAX);
if ([string sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:mode].height > labelHeight)
{
NSUInteger index = 0;
NSUInteger prev;
NSCharacterSet *characterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
do
{
prev = index;
if (mode == UILineBreakModeCharacterWrap)
index++;
else
index = [string rangeOfCharacterFromSet:characterSet options:0 range:NSMakeRange(index + 1, [string length] - index - 1)].location;
}
while (index != NSNotFound && index < [string length] && [[string substringToIndex:index] sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:mode].height <= labelHeight);
return prev;
}
return [string length];
}
Perhaps the character set used here isn't quite right (should you include hyphens, for example), but it's probably pretty close and far more efficient than doing character by character, if you don't need to do that.