Is there a way to limit the width of text in UITextView - objective-c

I have UITextView where I need to control where text start and end.
This because I use bubble as background image for UITextView and I need to display the text inside the bubble image.
So far,I have been able to control where text start using:
self.contentOffset = (CGPoint){.x = -10, .y = -10};
but I have no clue how to control where text end. Currently, by default it uses all UITextView width.

Will the bubble grow in height as the text stretches? Or will the bubble be limited to 1 line? Either way, I think you should use a different approach to deal with this problem.
Use -sizeWithFont:constainedToSize:lineBreakMode: on an NSString instance to figure out what the size is of the text. If the size is greater then the value allowed by you, stop allowing input (if that's your goal) or increase hight of the bubble and allow text to continue on next line.
You would typically perform these recalculations upon text input, which means you'd implement the UITextViewDelegate method -textView:shouldChangeTextInRange:replacementText: according to the above suggestions.
-- or --
Just limiting the size of the textView and setting the lineBreakMode & numberOfLines might also be sufficient for your purposes.

define #define TEXT_LENGTH 25 // Whatever your limit is
The UITextView calls this method whenever the user types a new character or deletes an existing character.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
NSUInteger newLength = (textView.text.length - range.length) + text.length;
if(newLength <= TEXT_LENGTH)
{
return YES;
} else {
NSUInteger mySpace = TEXT_LENGTH - (textView.text.length - range.length);
textView.text = [[[textView.text substringToIndex:range.location]
stringByAppendingString:[text substringToIndex: mySpace]]
stringByAppendingString:[textView.text substringFromIndex:(range.location + range.length)]];
return NO;
}
}
Use replacementText not replacementString in method, so careful with the different between replacementString and replacementText.

Related

Equal sized cells in NSSegmentedControl

For my NSSegmentedControl, I use it do display a bar to control a NSTableView. I have code set up to control the size programmatically:
for (NSInteger i = 1; i <= numberOfSegments; i++) {
CGSize textSize = [[NSString stringWithFormat:#"Workspace %ld", (long)i] sizeWithAttributes:#{NSFontAttributeName: [NSFont systemFontOfSize:13.0f]}];
NSInteger segmentWidth = self.workspaceControl.frame.size.width / numberOfSegments;
if (textSize.width > segmentWidth) {
[self.workspaceControl setLabel:[NSString stringWithFormat:#"%ld", (long)i] forSegment:i - 1];
} else {
[self.workspaceControl setLabel:[NSString stringWithFormat:#"Workspace %ld", (long)i] forSegment:i - 1];
}
[self.workspaceControl setWidth:segmentWidth forSegment:i - 1];
}
This works, by a small problem occurs.
At the beginning (with one segment) it looks like this:
As I change the value, the right side gets clipped slightly.
And then back to one segment:
The constraints are as follows:
Im very puzzled by the clipping (probably because a couple pixels t0o large), but I have no idea how to fix it, or is there a better way to get evenly spaced cells?
In my testing the generated width constraints of the NSSegmentedControl segments appear to round to whole numbers despite setWidth(_ width: CGFloat, forSegment segment: Int) taking a CGFloat. Thus the total width of the segments can add up to more than the width of the control itself.
To get even segments pass segmentWidth through the floor. Also, set a manual width for the control and constrain it horizontally by the centerXAnchor instead of by the leading and trailing anchors to preserve its center-alignment.
Drawbacks: the width must be hard-coded and the left and right margins may not match your other views exactly.

Check if text in UITextView went to new line due to word wrap

How can I check if the text in a UITextView went to the next line due to word wrap?
I currently have code to check if the user enters a new line (from the keyboard).
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
{
if ( [text isEqualToString:#"\n"] ) {
...
}
return YES;
}
You can use the contentSize.height property of UITextView to determine how 'tall' your textview is. You can then divide by the lineHeight to figure out the number of lines. #nacho4d provided this nifty line of code that does just that:
int numLines = textView.contentSize.height/textView.font.lineHeight;
Refer to this question for more info:
How to read number of lines in uitextview
EDIT:
So you could do something like this inside -textViewDidChange: in order to check for a line change.
int numLines = textView.contentSize.height / textView.font.lineHeight;
if (numLines != numLinesInTextView){
numLinesInTextView = numLines;//numLinesInTextView is an instance variable
//then do whatever you need to do on line change
}

UILabel truncate text not working

I set the numberOfLines to 1 in the IB, but when I set the text to a long string, it doesn't truncate. If I set the numberOfLines to 2, the truncate works fine.What should I do to truncate a long string into a single line?
simple, set the following properties:
label.adjustsFontSizeToFitWidth = NO;
label.lineBreakMode = NSLineBreakByTruncatingTail;
If you're using attributed string and having set paragraph style to the attributed string: make sure you're passing paragraphStyle.lineBreakMode = .byTruncatingTail.
If using auto layout, in my case, there was a constraint missing. The UILabel grows its width if no constraint is set on its width/trailing. Once its width is limited, for instance to its superview, the truncation occurs.
You probably have a constraint on a label in that section that is making things go haywire.
Double check your constraints or remove them for that label or other controls in that section.
The storyboard option for a label: "Line Breaks:Truncate Tail" will to the work you are looking for.
If you set the label's autoshrink to "Fixed Font Size" in IB, you will always get a truncatation when the string width beyond the label width. I guess you happened to set that to "Minimum Font Scale" or "Minimum Font Font", which will lead a resizing when the string is too long.
(Xcode 4.5, other version of Xcode and IB may be different property name)
I make two functions, that will help you to do your work.
Basic:
This solution I made for task:
"minimize font to my min size of font and then put as much info, as possible, but not bigger then maximum width"
takeFineFont... function parameters:
(UIFont*)font - font of the your label (titleLabel.font)
(NSString*)string - text in your label (titleLabel.text)
(CGSize)limitStringSize - limit size.
limitStringSize.width - width limit of your label (Upper limit)
limitStringSize.height - height limit of your label (Lower limit)(actually, size of font)
-(UIFont*)takeFineFontSize:(UIFont*)font
forText:(NSString*)string
andLimit:(CGSize)limitStringSize{
UIFont* resultFont = [UIFont fontWithName:[font fontName] size:[font pointSize]];
if(limitStringSize.width != 0 && limitStringSize.height != 0){
CGSize currentSize = [string sizeWithFont:resultFont];
while(/* change font width with upper bound */
currentSize.width > limitStringSize.width
&&
/* change font height with lower bound */
currentSize.height > limitStringSize.height){
/*change height and take new width*/
currentSize.height -= 1;
currentSize.width = [string sizeWithFont:[resultFont fontWithSize:currentSize.height]].width;
}
resultFont = [resultFont fontWithSize:currentSize.height];
}
return resultFont;
}
-(double)takeFineWidthForFont:(UIFont*)font
forString:(NSString*)string
andLimit:(double)widthLimit{
return MIN([string sizeWithFont:font].width, widthLimit);
}
Suppose, that you have big string in UILabel* titleLabel
And you define somewhere:
#define maximumLengthOfYourLabel 300
#define minimumSizeOfFont 14
what you will do now? just do this peace of code:
-(void)updateTitleLabelWithBigText:(NSString*)string{
/*change text*/
self.titleLabel.text = string;
/*take pretty small font*/
self.titleLabel.font = [self takeFineFontSize:self.titleLabel.font
forText:self.titleLabel.text
andLimit:CGSizeMake(maximumLengthOfYourLabel,minimumSizeOfFont)
];
/*if your text still big, take minimal width and trunctate it*/
self.titleLabel.width = [self takeFineWidthForFont:self.titleLabel.font
forString:self.titleLabel.text
andLimit:maximumLengthOfYourLabel];
self.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
}
Ensure you are not calling sizeToFit() on your label. It will override the label and constraint settings.
set numberOfLines to 0, this will let you expand uilabel content vertially without truncating its width.
Perhaps this method can help you:
[myLabel sizeToFit];
The label won't be truncated but it will adjust the label size to fit in one line.

How to Read Number of lines in UITextView

I am using UITextView In my View , I have requirement to count number of line contained by textview am using following function to read '\n' . However this works only when return key is pressed from keyboard , but in case line warapper (when i type continuous characters i wont get new line char ) . How do i read new char when line is changed without hitting return key ?? Anybody has nay idea how to .. please share it ..
I am follwing this link Link
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
// Any new character added is passed in as the "text" parameter
if ([text isEqualToString:#"\n"]) {
// Be sure to test for equality using the "isEqualToString" message
[textView resignFirstResponder];
// Return NO so that the final '\n' character doesn't get added
return NO;
}
// For any other character return YES so that the text gets added to the view
return YES;
}
In iOS 7, it should exactly be:
float rows = (textView.contentSize.height - textView.textContainerInset.top - textView.textContainerInset.bottom) / textView.font.lineHeight;
You can look at the contentSize property of your UITextView to get the height of the text in pixels, and divide by the line height spacing of the UITextView's font to get the number of text lines in the total UIScrollView (on and off screen), including both wrapped and line broken text.
extension NSLayoutManager {
var numberOfLines: Int {
guard let textStorage = textStorage else { return 0 }
var count = 0
enumerateLineFragments(forGlyphRange: NSMakeRange(0, numberOfGlyphs)) { _, _, _, _, _ in
count += 1
}
return count
}
}
Get number of lines in textView:
let numberOfLines = textView.layoutManager.numberOfLines
Just for reference...
Another way you can get the number of lines and also the content, is splitting the lines in an array using the same method mentioned on the answer edit by BoltClock.
NSArray *rows = [textView.text componentsSeparatedByString:#"\n"];
You can iterate through the array to get the content of each line and you can use the [rows count] to get the exact number of rows.
One thing to keep in mind is that empty lines will be counted as well.

Limiting text in a UITextView

I am trying to limit the text input into a UITextView in cocoa-touch. I really want to limit the amount of rows rather than the number of characters. So far I have this to count the amount of rows:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:#"\n"]) {
rows++;
}
NSLog(#"Rows: %i", rows);
return YES;
}
However this doesn't work if the line is automatically wrapped rather than the user pressing the return key. Is there a way to check if the text was wrapped similar to checking for "\n"?
Thanks.
Unfortunately, using NSString -stringWithFont:forWidth:lineBreakMode: doesn't work - which ever wrap mode you choose, the text wraps with a width that is less than the current width, and the height becomes 0 on any overflow lines. To get a real figure, fit the string into a frame that is taller than the one you need - then you'll get a height that is greater than your actual height.
Note my fudge in this (subtracting 15 from the width). This might be something to do with my views (I have one within another), so you might not need it.
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)aRange replacementText:(NSString*)aText
{
NSString* newText = [aTextView.text stringByReplacingCharactersInRange:aRange withString:aText];
// TODO - find out why the size of the string is smaller than the actual width, so that you get extra, wrapped characters unless you take something off
CGSize tallerSize = CGSizeMake(aTextView.frame.size.width-15,aTextView.frame.size.height*2); // pretend there's more vertical space to get that extra line to check on
CGSize newSize = [newText sizeWithFont:aTextView.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
if (newSize.height > aTextView.frame.size.height)
{
[myAppDelegate beep];
return NO;
}
else
return YES;
}