Obj C - Word Line Truncation - objective-c

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];
}

Related

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.

NSMutableArray inside setTitle

I've stored 4 unique numbers inside an NSMutableArray from 1-4. i've done that by using this code:
storeArray = [[NSMutableArray alloc] init];
BOOL record = NO;
int x;
for (int i=1; [storeArray count] < 4; i++) //Loop for generate different random values
{
x = arc4random() % 4;//generating random number
if(i==1)//for first time
{
[storeArray addObject:[NSNumber numberWithInt:x]];
}
else
{
for (int j=0; j<= [storeArray count]-1; j++)
{
if (x ==[[storeArray objectAtIndex:j] intValue])
record = YES;
}
if (record == YES)
{
record = NO;
}
else
{
[storeArray addObject:[NSNumber numberWithInt:x]];
}
}
}
I can then print the numbers out using storeArray[1] and so on.
the problem is i want to print the numbers inside this.
[option1 setTitle:questions[r][storeArray[0]] forState: UIControlStateNormal];
[option2 setTitle:questions[r][storeArray[1]] forState: UIControlStateNormal];
[option3 setTitle:questions[r][storeArray[2]] forState: UIControlStateNormal];
[option4 setTitle:questions[r][storeArray[3]] forState: UIControlStateNormal];
How can i do this?, cause i when i do this i get thread sigbrt error?
The problem is that your algorithm is faulty and when there are collisions because you are trying to keep the numbers unique, you don't record anything so your array might not always be the expected length. Actually there is a 91% chance of this happening in your case so it looks like it happens all the time.
Instead of trying to write your own algorithm, just use the existing classes. Simply use an NSSet to guarantee the uniqueness of the numbers in your array.
NSMutableSet *set = [[NSMutableSet alloc] init];
while(set.count < 4) {
int x = arc4random() % 4;
[set addObject:[NSNumber numberWithInteger:x]];
}
NSArray * storeArray = [set allObjects];

Resize UITextField Based On Text Entered

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;

Determing if a NSString has all unique characters

(For example, "assdf" and "aash" would be considered false).
I think you stated the problem backwards, but to test if every character in an NSString is unique, I think the following should work. There may be some funny unicode edge cases that don't work, where identical glyphs show up as different code points.
#interface NSString(_uniqueChars)
-(BOOL) isEveryCharacterUnique;
#end
#implementation NSString(_uniqueChars)
-(BOOL) isEveryCharacterUnique
{
NSMutableSet *set = [NSMutableSet setWithCapacity:self.length];
for ( NSUInteger i = 0; i < self.length; ++i )
{
unichar c = [self characterAtIndex:i];
[set addObject:[NSNumber numberWithUnsignedShort:c]];
}
return (set.count == self.length);
}
#end
I also was trying NSCharacterSet but no results. This could be another solution but +1 for a better one.
- (BOOL) isUnique: (NSString *) aString{
int len = (int)aString.length;
for(int i=0;i<len;i++)
{
NSString *tmp = [aString substringWithRange:NSMakeRange(i, 1)];
for(int j=i+1;j<len;j++)
{
if([[aString substringWithRange:NSMakeRange(j, 1)]isEqualToString: tmp])
{
return NO;
}
}
}
return YES;
}

How do I divide NSString into smaller words?

Greetings,
I am new to objective c, and I have the following issue:
I have a NSString:
"There are seven words in this phrase"
I want to divide this into 3 smaller strings (and each smaller string can be no longer than 12 characters in length) but must contain whole words separated by a space, so that I end up with:
String1 = "There are" //(length is 9 including space)
String2 = "seven words"// (length is 11)
String3 = "in this" //(length is 7), with the word "phrase" ignored as this would exceed the maximum length of 12..
Currently I am splitting my original array into an array with:
NSArray *piecesOfOriginalString = [originalString componentsSeparatedByString:#" "];
Then I have multiple "if" statements to sort out situations where there are 3 words, but I want to make this more extensible for any array up to 39 (13 characters * 3 line) letters, with any characters >40 being ignored. Is there an easy way to divide a string based on words or "phrases" up to a certain length (in this case, 12)?
Something similar to this? (Dry-code warning)
NSArray *piecesOfOriginalString = [originalString componentsSeparatedByString:#" "];
NSMutableArray *phrases = [NSMutableArray array];
NSString *chunk = nil;
NSString *lastchunk = nil;
int i, count = [piecesOfOriginalString count];
for (i = 0; i < count; i++) {
lastchunk = [[chunk copy] autorelease];
if (chunk) {
chunk = [chunk stringByAppendingString:[NSString stringWithFormat:#" %#", [piecesOfOriginalString objectAtIndex:i]]];
} else {
chunk = [[[piecesOfOriginalString objectAtIndex:i] copy] autorelease];
}
if ([chunk length] > 12) {
[phrases addObject:lastchunk];
chunk = nil;
}
if ([phrases count] == 3) {
break;
}
}
well, you can keep splitting the string as you're already doing, or you could check out whether NSScanner suits your needs. In any case, you're going to have to do the math yourself.
Thanks McLemore, that is really helpful! I will try this immediately. My current solution is very similar, but less refined, as I hard coded the loops and use individual variable to hold the sub strings (called them TopRow, MidRow, and BottomRow), that and the memory management issue is overlooked... :
int maxLength = 12; // max chars per line (in each string)
int j=0; // for looping, j is the counter for managing the words in the "for" loop
TopRow = nil; //1st string
MidRow = nil; //2nd string
//BottomRow = nil; //third row string (not implemented yet)
BOOL Row01done = NO; // if YES, then stop trying to fill row 1
BOOL Row02done = NO; // if YES, then stop trying to fill row 2
largeArray = #"Larger string with multiple words";
tempArray = [largeArray componentsSeparatedByString:#" "];
for (j=0; j<[tempArray count]; j=j+1) {
if (TopRow == nil) {
TopRow = [tempArray objectAtIndex:j];
}
else {
if (Row01done == YES) {
if (MidRow == nil) {
MidRow = [tempArray objectAtIndex:j];
}
else {
if (Row02done == YES) {
//row 3 stuff goes here... unless I can rewrite as iterative loop...
//will need to uncommend BottomRow = nil; above..
}
else {
if ([MidRow length] + [[tempArray objectAtIndex:j] length] < maxLength) {
MidRow = [MidRow stringByAppendingString:#" "];
MidRow = [MidRow stringByAppendingString:[tempArray objectAtIndex:j]];
}
else {
Row02done = YES;
//j=j-1; // uncomment once BottowRow loop is implemented
}
}
}
}
else {
if (([TopRow length] + [[tempArray objectAtIndex:j] length]) < maxLength) {
TopRow = [TopRow stringByAppendingString:#" "];
TopRow = [TopRow stringByAppendingString:[tempArray objectAtIndex:j]];
}
else {
Row01done = YES;
j=j-1; //end of loop without adding the string to TopRow, subtract 1 from j and start over inside Mid Row
}
}
}
}