I have a UITextView which is designed to enlarge to fit the contentView when needed. When I paste in a paragraph of text, however, it puts the start and end points of the content vertically in the wrong places. Entering or deleting a character resets it back to the correct position.
Any ideas why this is?
-(void)textViewDidChange:(UITextView *)textView {
self.textView.frame = CGRectMake(
self.textView.frame.origin.x,
self.textView.frame.origin.y,
self.textView.frame.size.width,
self.textView.contentSize.height + HEADER_ADDITIONAL_HEIGHT);
self.textView.contentOffset = CGPointMake(0, 0);
self.previousContentSize = textView.contentSize;
}
When I used:
textView.contentSize = textView.frame.size;
textView.contentOffset = CGPointZero;
It solved my issue, but created a new issue where we sometimes get weird scrolling while typing or deleting text. So, I used this:
textView.contentSize = CGSizeMake( textView.contentSize.width,
textView.contentSize.height+1);
This also solved the issue. I think what we all need here is the effect which we get whenever the contentSize of a textview is changed. Unfortunately, I do not know what this effect is. If somebody knows, please tell.
Update:
I have found a method which you can use to solve your issue (I used this to resolve mine).
You can ask NSLayoutMAnager to refresh the entire layout:
[textView.textStorage edited:NSTextStorageEditedCharacters range:NSMakeRange(0, textView.textStorage.length) changeInLength:0];
NSLayoutManager attempts to avoid refreshing the layout because it's time consuming and takes a lot of work, so it's set up to only do it when absolutely necessary (lazily).
There are a number of invalidateLayout functions related to this class but none of them cause an actual re-layout when called.
I know this comes late, but I ran into this issue and thought I should share what I came up with in case others find themselves in the same situation.
You are on the right track, but in textViewDidChange: you are missing one important thing: setting the contentSize after updating the frame height.
// I used 0.f for the height, but you can use another value because according to the docs:
// "the actual bounding rectangle returned by this method can be larger
// than the constraints if additional space is needed to render the entire
// string. Typically, the renderer preserves the width constraint and
// adjusts the height constraint as needed."
CGSize size = CGSizeMake(textview.frame.size.width, 0.f);
CGRect rect = [string boundingRectWithSize:size
options:OptionsYouNeedIfAny // NSStringDrawingOptions
context:nil];
// Where MinTextViewHeight is the smallest height for a textView that
// your design can handle
CGFloat height = MAX(ceilf(rect.size.height), MinTextViewHeight);
CGRect rect = textView.frame;
rect.size.height = height;
textView.frame = rect;
// Adjusting the textView contentSize after updating the frame height is one of the things you were missing
textView.contentSize = textView.frame.size;
textView.contentOffset = CGPointZero;
I hope this helps!
See the docs for more info about using boundingRectWithSize:options:context:.
Related
I have used four UITextviews to place each paragraphs in. Now I want to add a UIButton as subview for each UITextview.
I realized creating subview with frame CGRectMake(textview.frame.width-50,textview.frame.height-20,50,20) is not a ideal solution because the sentence may end anywhere, I mean the last character of any paragraph may end anywhere, its position is not constant though.
So the bottom line is, I want to add UIButton within the UITextView,just next to lats word. How do I do that?
Any solution will be greatly appreciated. :)
It's not so simple.
Using:
CGSize sizeOfString = [string sizeWithFont:self.aTextView.font constrainedToSize:self.aTextView.frame.size];
you can obtain a CGSize for the given string.
But, giving the entire string, will result in a size equal to your UITextView size (so, the bottom-right corner)
You have to use this method on the last line of each UITextField...and this is the difficult part. The word wrap is done by CoreText, UITextView or UILabel doesn't give you information about the single lines, positions, etc.
You have to calculate it by yourself doing something similar (the code is not so clean, I'll help later if you have problem understanding):
NSAttributedString* text = self.aTextView.attributedText;
CTFramesetterRef fs =
CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)text);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0,0,self.aTextView.frame.size.width - 16,100000)); // why the -16? the frame should be the REAL TEXT FRAME, not the UITextView frame. If you look, there is a space between the view margin and the text. This is the baseline. Probably there is a method to calculate it programatically, but I can't check now. In my case it seems like 8px (*2)
CTFrameRef f = CTFramesetterCreateFrame(fs, CFRangeMake(0, 0), path, NULL);
CTFrameDraw(f, NULL);
NSRange lastRange;
NSArray* lines = (__bridge NSArray*)CTFrameGetLines(f);
id lastLineInArray = [lines lastObject];
CTLineRef theLine = (__bridge CTLineRef)lastLineInArray;
CFRange range = CTLineGetStringRange(theLine);
lastRange.length = range.length;
lastRange.location = range.location - 1;
NSLog(#"%ld %ld", range.location, range.length);
CGPathRelease(path);
CFRelease(f);
CFRelease(fs);
// this is the last line
NSString *lastLine = [self.aTextView.text substringWithRange:lastRange];
Now, you can use:
CGSize sizeOfString = [lastLine sizeWithFont:self.aTextView.font constrainedToSize:self.aTextView.frame.size];
you will obtain the width and the height of the string, and then the final position of your button (for the y position: number of lines taken from the lines array count * string height)
EDIT: a comment about the left space in the UITextView (the reason why there is a -16 in this line)
CGPathAddRect(path, NULL, CGRectMake(0,0,self.aTextView.frame.size.width - 16,100000));
It happens because the text is not really inside the UITextView, but inside one of its subviews: an UIWebDocumentView (private class) which adds an insets. After some searching, I can't find any method to obtain (legally) the value of this insets, necessary to pass the correct rect to the CGPathAddRect() function.
You can do some tests to be sure that it's always 8pt :-) or switch to UILabel, which doesn't have that content insets
You should consider either using UIWebView or some third party attributed text views that support such features.
UITextView internally uses a web view, making it a nontrivial job to figure out the exact layout of text.
I think, that better way to use tableView. One paragraph in one cell. Make cell without border and set clearColor to cells background. In this way you can easy add button.
I am notes.app like app. I am setting UITextView's contentInset as
myTextView.contentInset = UIEdgeInsetsMake(0, 25, 0, -25);
myTextView.contentSize = CGSizeMake(295, myTextView.contentSize.height);
but that does not seems to work as I expected. Two characters are hidden.
I get this
but if I set
myTextView.contentInset = UIEdgeInsetsMake(0, 25, 0, 0);
myTextView.contentSize = CGSizeMake(295, myTextView.contentSize.height);
I get actual text as
please help.
fibnochi you can use a different approach to do this. I have used this in one of my app and it is working fine
First of all you need to get the size of UItextView which will carry all the data, for this you need to use NSString class delegate function
NSString* str = #"Your Entire string will come here";
CGSize size = [str sizeWithFont:[UIFont systemFontOfSize:17.0] constrainedToSize:CGSize(295, any large digit for maximum height say 10000) lineBreakMode:UILineBreakModeWordWrap];
textView.contentSize = size;
Will this solve your problem or not. Please let me know
Can you please try the below?
myTextView.contentInset = UIEdgeInsetsMake(0, 25, 0, 25);
myTextView.contentSize = CGSizeMake(295, myTextView.contentSize.height);
Apple's discussions
here.
"
Edge inset values are applied to a rectangle to shrink or expand the area represented by that rectangle. Typically, edge insets are used during view layout to modify the view’s frame. Positive values cause the frame to be inset (or shrunk) by the specified amount. Negative values cause the frame to be outset (or expanded) by the specified amount.
"
You can do like UIEdgeInsetsMake(0,25,0,25)
try this code :)
myTextView.contentInset = UIEdgeInsetsMake(0, 25, 0, -25);
myTextView.contentSize = CGSizeMake(270, myTextView.contentSize.height);
May this will help you out :)
I have a UILabel that is a fixed size. Unfortunately on rare occasions, the text I need to fit into it doesn't fit! I have tried reducing the font size, but it needs to reduce so much that it looks terrible.
Is it possible to change the font width somehow? UIFont does not seem to have any properties to allow me to do this? Do I need to use a UIWebView and use CSS? I don't know much CSS, so any help is much appreciated if this is the best way to solve this.
Alternatively, any other ways to solve this?
Thanks Craig
The simplest way to shrink just the width of the text is to apply a transform to the label's layer:
label.layer.transform = CATransform3DMakeScale(desiredWidth/textWidth, 1.0, 1.0);
Do you mean you want to squeeze it horizontally while keeping the height? This is achievable, up to about 60% of the regular width. Beyond that it looks terrible.
Here is the drawRect for a UILabel subclass which squeezes independently on either axis if necessary.
// This drawRect for a UILabel subclass reproduces most common UILabel formatting, but does not do truncation, line breaks, or scaling to fit.
// Instead, it identifies cases where the label text is too large on either axis, and shrinks along that axis.
// For small adjustments, this can keep text readable. In extreme cases, it will create an ugly opaque block.
- (void) drawRect:(CGRect)rect;
{
CGRect bounds = [self bounds];
NSString *text = [self text];
UIFont *font = [self font];
// Find the space needed for all the text.
CGSize textSize = [text sizeWithFont:font];
// topLeft is the point from which the text will be drawn. It may have to move due to compensate for scaling, or due to the chosen alignment.
CGPoint topLeft = bounds.origin;
// Default to no scaling.
CGFloat scaleX = 1.0;
CGFloat scaleY = 1.0;
// If the text is too wide for its space, reduce it.
// Remove the second half of this AND statement to have text scale WIDER than normal to fill the space. Useless in most cases, but can be amusing.
if ((textSize.width>0) && (bounds.size.width/textSize.width<1))
{
scaleX = bounds.size.width/textSize.width;
topLeft.x /= scaleX;
}
else
{
// Alignment only matters if the label text doesn't already fill the space available.
switch ([self textAlignment])
{
case UITextAlignmentLeft :
{
topLeft.x = bounds.origin.x;
}
break;
case UITextAlignmentCenter :
{
topLeft.x = bounds.origin.x+(bounds.size.width-textSize.width)/2;
}
break;
case UITextAlignmentRight :
{
topLeft.x = bounds.origin.x+bounds.size.width-textSize.width;
}
break;
}
}
// Also adjust the height if necessary.
if ((textSize.height>0) && (bounds.size.height/textSize.height<1))
{
scaleY = bounds.size.height/textSize.height;
topLeft.y /= scaleY;
}
else
{
// If the label does not fill the height, center it vertically.
// A common feature request is for labels that do top or bottom alignment. If this is needed, add a property for vertical alignment, and obey it here.
topLeft.y = bounds.origin.y+(bounds.size.height-textSize.height)/2;
}
// Having calculated the transformations needed, apply them here.
// All drawing that follows will be scaled.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleX, scaleY);
// Begin drawing.
// UILabels may have a shadow.
if ([self shadowColor])
{
[[self shadowColor] set];
CGPoint shadowTopLeft = CGPointMake(topLeft.x+[self shadowOffset].width/scaleX, topLeft.y+[self shadowOffset].height/scaleY);
[text drawAtPoint:shadowTopLeft withFont:font];
}
// The text color may change with highlighting.
UIColor *currentTextColor;
if ((![self isHighlighted]) || (![self highlightedTextColor]))
currentTextColor = [self textColor];
else
currentTextColor = [self highlightedTextColor];
// Finally, draw the regular text.
if (currentTextColor)
{
[currentTextColor set];
[text drawAtPoint:topLeft withFont:font];
}
}
You can set the minimum font size of a UILabel to a smaller value, and check Autoshrink to let it automatically shrink. This parameter is available in Interface Builder.
The internal implementation will reduce kerning, which is the width of space between characters. It cannot actually reduce width though.
This is your better bet. If you are still unsatisfied with results. You may have to change your design.
I need to create a custom UILabel to display some dynamic multiline text. The text color is white on a black background. But the background should only be visible right behind the text to simulate the effect of an selected text area.
I started with subclassing UILabel and overriding drawTextInRect to do my own drawings.
- (void) drawTextInRect:(CGRect)rect
{
/* do some custom drawings here */
[super drawTextInRect:rect];
}
So far i could not figure out a way to compute the text-bounds do draw my background into.
Does anybody now how do do this kind of stuff? Thanks a lot.
NSString has some additions in UIKit to allow for calculating the size used to render the string, given various parameters. You can use these methods to calculate the size the UILabel needs to render the string, and then resize the UILabel to precisely this size. Look in the documentation for a method called -sizeWithFont:, all of the other variations are listed there. Make sure you use the right method to match how your UILabel is configured.
There is one caveat here, which is that on iOS 4, there is a bug where these methods actually return a slightly different size than is actually used for drawing (at least for the system font on iPhone 4's [e.g. Helvetica Neue], I don't know if this bug affects any other fonts). Unfortunately the only workaround I know of is to switch to Core Text for all your text rendering, so you may just prefer to live with this bug (if it even affects you) until Apple pushes out a software update. This bug does affect Apple's own applications so there is plenty of precedent for not handling it.
Ok. Now after some input from stackoverflow i am using this code snippet to get the textbounds but it only works fine with single line text.
- (void) drawTextInRect:(CGRect)rect
{
CGFloat lineHeight = [self.text sizeWithFont:self.font].height;
CGSize testSize = CGSizeMake(320, lineHeight * self.numberOfLines);
CGSize textSize = [self.text sizeWithFont:self.font constrainedToSize:testSize lineBreakMode:self.lineBreakMode];
//NSLog(#"drawTextInRect lineHeight %f, width %f x height %f", lineHeight, textSize.width, textSize.height);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1.0);
CGContextAddRect(context, CGRectMake(0, 0, textSize.width, textSize.height));
CGContextFillPath(context);
CGContextRestoreGState(context);
[super drawTextInRect:rect];
}
If i have multiline text i still need some code to compute the path-information of the text and not just the bounding rect so that i can use CGContextDrawPath to draw my background. Any thoughts on that? Thanks.
I have a UIImageView that displays a bigger image. It appears to be centered, but I would like to move that image inside that UIImageView. I looked at the MoveMe sample from Apple, but I couldn't figure out how they do it. It seems that they don't even have an UIImageView for that. Any ideas?
What you need is something like (e.g. showing the 30% by 30% of the top left corner of the original image):
imageView.layer.contentsRect = CGRectMake(0.0, 0.0, 0.3, 0.3);
Description of "contentsRect":
The rectangle, in the unit coordinate space, that defines the portion of the layer’s contents that should be used.
Original Answer has been superseded by CoreAnimation in iOS4.
So as Gold Thumb says: you can do this by accessing the UIView's CALayer. Specifically its contentRect:
From the Apple Docs: The rectangle, in the unit coordinate space, that defines the portion of the layer’s contents that should be used. Animatable.
Do you want to display the image so that it is contained within the UIImageView? In that case just change the contectMode of UIImageView to UIViewContentModeScaleToFill (if aspect ratio is inconsequential) or UIViewContentModeScaleAspectFit (if you want to maintain the aspect ratio)
In IB, this can be done by setting the Mode in Inspector.
In code, it can be done as
yourImageView.contentMode = UIViewContentModeScaleToFill;
In case you want to display the large image as is inside a UIImageView, the best and easiest way to do this would be to have the image view inside a UIScrollView. That ways you will be able to zoom in and out in the image and also move it around.
Hope that helps.
It doesn't sound like the MoveMe sample does anything like what you want. The PlacardView in it is the same size as the image used. The only size change done to it is a view transform, which doesn't effect the viewport of the image. As I understand it, you have a large picture, and want to show a small viewport into it. There isn't a simple class method to do this, but there is a function that you can use to get the desired results: CGImageCreateWithImageInRect(CGImageRef, CGRect) will help you out.
Here's a short example using it:
CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect);
[UIImageView setImage:[UIImage imageWithCGImage:imageRef]];
CGImageRelease(imageRef);
Thanks a lot. I have found a pretty simple solution that looks like this:
CGRect frameRect = myImage.frame;
CGPoint rectPoint = frameRect.origin;
CGFloat newXPos = rectPoint.x - 0.5f;
myImage.frame = CGRectMake(newXPos, 0.0f, myImage.frame.size.width, myImage.frame.size.height);
I just move the frame around. It happens that portions of that frame go out of the iPhone's view port, but I hope that this won't matter much. There is a mask over it, so it doesn't look weird. The user doesn't totice how it's done.
You can accomplish the same by:
UIImageView *imgVw=[[UIImageView alloc]initwithFrame:CGRectMake(x,y,height,width)];
imgVw.image=[UIImage imageNamed:#""];
[self.view addSubView imgVw];
imgVw.contentMode = UIViewContentModeScaleToFill;
You can use NSLayoutConstraint to set the position of UIImageView , it can be relative to other elements or with respect to the frame.
Here's an example snippet:
let logo = UIImage(imageLiteralResourceName: "img")
let logoImage = UIImageView(image: logo)
logoImage.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(logoImage)
NSLayoutConstraint.activate([logoImage.topAnchor.constraint(equalTo: view.topAnchor,constant: 30),
logoImage.centerXAnchor.constraint(equalTo: view.centerXAnchor),logoImage.widthAnchor.constraint(equalToConstant: 100),logoImage.heightAnchor.constraint(equalToConstant: 100)
])
This way you can also resize the image easily. The constant parameter represents, how far should a certain anchor be positioned relative to the specified anchor.
Consider this,
logoImage.topAnchor.constraint(equalTo: view.topAnchor,constant: 30)
The above line is setting the top anchor of the instance logoImage to be 30 (constant) below the parent view. A negative value would mean opposite direction.