Autoresize UITextView and UIScrollView - iphone

I already did several searches on Stack Overflow and Google, but I couldn't find a solution to my problem.
I have a Detail View for a product that includes a UIScrollView and a few subviews (UIImageView, UILabel, UITextView). You can find an example here.
First I wanna autoresize the UITextView (not the text itself!) to the corresponding height. Then I wanna autoresize the entire UIScrollView so that it fits the height of my UITextView and the elements above it. I've tried the following code:
myUITextView.frame = CGRectMake(2.0, 98.0, 316.0, self.view.bounds.size.height);
[scrollView setContentOffset:CGPointMake(0.0, 0.0) animated:NO];
scrollView.contentSize = CGSizeMake(320.0, 98.0 + [myUITextView frame].size.height);
[self.view addSubview:scrollView];
98.0 + [myUITextView frame].size.height) because my thought was: After getting the height of myUITextView, add the height of the other subviews (98.0) to it.
Unfortunately it doesn't work very well. Depending on the content of the UIScrollView, it is too short or too long. I did some logging with the result that the height of the UITextView is always the same:
2010-01-27 14:15:45.096 myApp[1362:207] [myUITextView frame].size.height: 367.000000
Thanks!

There is actually a very easy way to do resize the UITextView to its correct height using its contentSize.
CGRect frame = _textView.frame;
frame.size.height = _textView.contentSize.height;
_textView.frame = frame;
One thing to note is that the correct contentSize is only available after the UITextView has been added to the view with addSubview. Prior to that it is equal to frame.size

A UITextView won't automatically resize itself to it's contents (not as far as I know anyway) so you need to get the size of the text yourself and size the UIView accordingly.
The functions on this page will help - you can use them to get the width and height of a string, something like
// Get the size that the string will render at
NSString *contents = #"This is the content of your UITextView";
CGSize areaSize = [contents sizeWithFont:myView.font forWidth:myView.frame.size.width lineBreakMode:UILineBreakModeWordWrap];
// Then set the frame of your UITextView to be the right size
myView.bounds = CGRectMake(0, 0, areaSize.width, areaSize.height);
Then, you can layout the other components around it.
Hope this helps,
S
PS Warning, the link to the docs is correct but my code example is untested, sorry :)

Related

Resize UIView to fit its content

The problem is, that I do have a view containing 3 parts.
The first part is a simple header - same height always!
The second part is a simple description line - same height always!
Now the problem is the third part. It's a viewContainer for dynamically calculated subviews (each of them having a custom controller). The height of the content is dynamic caused by some text information downloaded from a backend. So some times I would need to scroll to be able to read the whole text, sometimes not.
Currently I am doing it this way:
Calculate the size of the UILabel for a specific text.
Then resize the parent view so fit the UILabel (if smaller).
Then resize the scrollView of my 3-Part-ViewController-View to fit its subviews.
The detail viewController with the dynamic content:
self.labelDescription.text = self.customData.descriptionText;
[self.labelDescription sizeToFit];
if(self.view.frame.size.height < (self.labelDescription.frame.size.height + self.labelDescription.frame.origin.y)) {
CGRect newSize = CGRectMake(0,
0,
self.view.frame.size.width,
self.labelDescription.frame.size.height +
self.labelDescription.frame.origin.y);
self.view.frame = newSize;
}
The resizing of the scroll view after adding and resizing my detail view:
[self addChildViewController:controllerCustomData];
[self.scrollView addSubview:controllerCustomData.view];
CGRect newRect = CGRectMake(0,
self.viewElementDetailContentContainer.frame.origin.y,
controllerCustomData.view.frame.size.width,
controllerCustomData.view.frame.size.height);
controllerCustomData.view.frame = newRect;
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, newRect.origin.y + newRect.size.height);
So my question is, are there easier ways to do this?
You could create a method that does everything you need. You'll need to write the method (maybe methodS) once and use it passing the necessary arguments.

How can I get the UITextView to scroll only when it's full of text

I have this UITextView that works great except, I can't get the text inside the UITextView to start scrolling only after the UITextView's size in nearly full, the UITextView is 4 lines tall, but as soon as I reach the 2nd line the 1st line is pushed up, I don't want the view to begin scrolling until I've reached the 5 line. scrollingEnabled = NO keeps it from scrolling at all, so that didn't work.
UITextView *barf_ = [[UITextView alloc] initWithFrame:CGRectMake(20.0, 310.0, 155, 50)];
barf_.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
//[barf_ scrollRangeToVisible:NSMakeRange([barf_.text length], 0)];
barf_.layer.cornerRadius = 3.0f;
barf_.layer.borderWidth = 0.5f;
barf_.font = [UIFont fontWithName:#"Helvetica" size:13];
I found the answer, as others with similar problems have mention, with a small textView, it automatically adds 32 padding to the bottom.
A simple fix is to add YourTextView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0); inside shouldChangeTextInRange method, that fixed my problem!
Setting the contentInset may help the text to appear more correctly within the UITextView. However, it won't help solve the issue whereby the UITextView has scrolling enabled despite not having more text to view.
Similarly, methods such as sizeWithFont have limitations. As explained in Mike Weller's excellent blog series iOS Development: You're Doing It Wrong, NSString isn't a good object to ask regarding how large a UIView should be. Many UIView subclasses such as UILabel, UIButton, etc. have insets and other considerations that must be accounted for during sizing. UITextLabel is no exception.
Mike Weller's particular entry on this subject is:
You're Doing It Wrong #2: Sizing labels with -[NSString sizeWithFont:...]
iOS 7 promises us more sophisticated text handling in UITextView, with properties such as textContainerInset. But what to do in the meantime?
Well, first we know that UITextView is a subclass of UIScrollView. Therefore, the golden rule that if the contentSize is larger than the view's bounds property, the scroll view will scroll so we can see more content.
Checking out contentSize agains the bounds won't work either because we know that UIScrollView is already calculating whether it should scroll or not based on the text, and it's giving us the wrong answer.
This is where arbitrary adjustment values come to the rescue! For me this value was 17.f. For you - depending on your fonts - it maybe different. We then take control and decide whether we should allow the scroll view to scroll:
static const CGFloat kArbritaryHeight = 17.f;
CGFloat adjustedContentHeight = myTextView.contentSize.height - kArbritaryHeight;
CGFloat boundsHeight = CGRectGetHeight(myTextView.bounds);
BOOL tooMuchContent = adjustedContentHeight > boundsHeight;
if (tooMuchContent)
{
myTextView.scrollEnabled = YES;
}
else
{
myTextView.scrollEnabled = NO;
}
When your UITextView is loaded set scrollEnabled to NO. Then set the text view's delegate to self or some other object and implement the UITextViewDelegate method
- (void)textViewDidChange:(UITextView *)textView
This method will get called anytime the user makes a change to the text inside the view. Inside this method you need to figure out how big your text is and if it goes beyond the bounds of the text view. If so you enable scrolling. Use this method:
- (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
This is a UIKit category method on NSString. It returns a CGSize that will tell you the height of whatever text string you call it on. In your case it would be something like
CGSize textSize = [textView.text sizeWithFont:textView.font
constrainedToSize:CGSizeMake(textView.frame.size.width, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap];
if (textSize.height > textView.frame.size.height) {
textView.scrollEnabled = YES;
} else {
textView.scrollEnabled = NO;
}
You might use the sizeWithFont:constrainedToSize:lineBreakMode: method to check whether your string will actually render larger than your text view and see if you need to enable scrolling. You will have to call it any time the text in your scrollview is set, however.
ex:
CGSize barfStringSize = [barfString sizeWithFont:[barf_ font]
constrainedToSize:CGSizeMake(barf_.bounds.size.width, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap]
[barf_ setScrollEnabled:barfStringSize.height > barf_.bounds.size.height]

UiScrollView with scrolling content

I have a detail page of type UIScrollView. On it I want an optional UIImageView and a mandatory UITextView. If no image is available then the image view should not take any space. The text for the text view can be of varying sizes.
When the view is loaded with, say, the image and the text I need to be able to scroll through all the contents. So the image slips off the top of the screen.
I just can't get this work and I feel it ought to be easy. Any ideas?
You must call setContentSize with the size of the views.
CGRect frameOfImage;
CGRect frameOfText;
CGRect frameOfContent;
frameOfImage.origin = CGPointZero;
frameOfImage.size = myImage ? [myImage size] : CGSizeZero;
[myImage setFrame:frameOfImage];
frameOfText.origin.x = 0;
frameOfText.origin.y = frameOfImage.origin.y + frameOfImage.size.height;
frameOfText.size = [myText.text sizeWithFont:myText.font forWidth:myScroll.bounds.width lineBreakMode:myText.lineBreakMode];
[myText setFrame:frameOfText];
frameOfContent = CGRectUnion( frameOfImage , frameOfText );
frameOfContent.size.height += frameOfContent.origin.y;
frameOfContent.size.width += frameOfContent.origin.x;
[myScroll setContextSize:frameOfContent.size];
You could do the last bit in the layoutSubviews of a custom UIScrollView or all of it at once in your controller when you know whether there is an image or not.
You should create your own view, which inherits after UIScrollView.
In your custom YourScrollView you should overwrite
- (void) layoutSubviews;
There calculate size of the text, size of the image (and additional spacing between then), and then set the contentSize for the scroll view using:
[self setContentSize:CGSizeMake(CGRectGetWidth(self.bounds), yourCalculatedHeight)];
Hope this helps,
Paul

UITextView change height instead of scroll

By default, the UITextView's contentView becomes scrollable when there is too much text to fit into the textview based on it's height.
I'd like to disable this and instead, update the height of the UITextView to fit in the text. The reason I'm doing this is because I'm adding the UITextView as a subview of a UIScrollView which should handle the scrolling, much like in the native Mail app (when you enter text, the whole view scrolls up, not just the textview.
Anyone got some ideas / has run into the same problem before?
It is very simply done like this:
CGRect frame = textView.frame;
frame.size = textView.contentSize;
textView.frame = frame;
This should set the height of the textview to appropriately fit all of its content.
Few little changes:
-(void)textViewDidChange:(UITextView *)textView {
CGFloat fontHeight = (textView.font.ascender - textView.font.descender) + 1;
CGRect newTextFrame = textView.frame;
newTextFrame.size = textView.contentSize;
newTextFrame.size.height = newTextFrame.size.height + fontHeight;
textView.frame = newTextFrame;
}
Adding the font height gives room for the autocorrection box when you spell something incorrectly.
The UITextView should also be set to not scroll:
[aTextView setScrollEnabled:NO];

sizeWithFont doesn't give correct height for UITextView if there is a long string in the text being wrapped

Is there a way to get the correct size of an NSString using:
- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode
that doesnt get thrown off by 2 or 3 hundred character strings. At the moment if I try to use this method on these long strings it incorrectly calculates them and I end up with lots of whitespace at the bottom of the UITextView.
I've tried using UILineBreakModeWordWrap and UILineBreakModeCharacterWrap.
the resizing is being done in
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat result = 44.0f;
NSString* text = nil;
CGFloat width = 0;
CGFloat tableViewWidth;
CGRect bounds = [UIScreen mainScreen].bounds;
tableViewWidth = bounds.size.width;
width = tableViewWidth - 150;
text = stringWithLongWords;
if (text) {
CGSize textSize = { width, 20000.0f };
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:10.0f] constrainedToSize:textSize lineBreakMode:UILineBreakModeWordWrap];
size.height += 50.0f;
result = MAX(size.height, 44.0f+30.0f);
}
return result;
}
UITextView is not exactly like a UILabel wrapped in a UIScrollView. It has line spacing different from the font size and margins that sizeWithFont:constrainedToSize:linkBreakMode: doesn't account for.
Knowing your font size you might be able to calculate the # of lines and take line spacing into account. You can guess at the margins and try to trick sizeWithFont: to give a more useful answer.
The popular solutions seem to be:
just use a UILabel if you don't need any UITextView functionality
if you need hyperlinks, overlay UIButtons that look like hyperlinks over a UILabel
use an off-screen UITextView and its sizeToFit method to get a real answer
I had no luck w/ the 3rd option but it sounds like it should work, so perhaps I did something wrong.
I'm going to try using a UILabel and overlaying buttons for hyperlinks. We'll see how that turns out.
If that fails, there is always the option taken by Loren Brichter (of Tweetie fame): draw everything into a UIView yourself using CoreGraphics.
Good luck!
Check out this post How do I size a UITextView to its content?
It looks like textView.contentSize.height should work (with the caveat that the the correct contentSize is only available after the UITextView has been added to the view with addSubview)
You said that you have a UITableView with differing heights. Have you set the reuse identifier to the same thing for all of the cells? It could be that older cells with their height already set are being reused. If this is the problem, you should resize the cell again when it's being reused.
The best solution I have found so far is to have a separate hidden UITextView with the same font settings, and set its text. After that its contetSize should be accurate.
The width you are using is the width for your UITextView... but you aren't concerned with that width, you are concerned with the width of the actual text area nested inside the text view.
UITextViews, by default, have padding around their borders to produce a space in-between the typed text and the edge of the UITextView a few pixels wide (and long for the top)... To get the correct size you shouldn't use
textView.frame.size.width
but rather,
textView.frame.size.width-(textView.contentInset.left+textView.contentInset.right+textView.textContainerInset.left+textView.textContainerInset.right+textView.textContainer.lineFragmentPadding/*left*/+textView.textContainer.lineFragmentPadding/*right*/)
^Which takes the width of the UITextView and subtracts out all the padding so you are left with the width of just the type-able text area.
Same goes for height except for lineFragmentPadding doesn't have a bottom so you only subtract it out once instead of twice.
The final code is something like this:
CGSize textViewContentSize = CGSizeMake(theTextView.frame.size.width-(theTextView.contentInset.left+theTextView.contentInset.right+theTextView.textContainerInset.left+theTextView.textContainerInset.right+theTextView.textContainer.lineFragmentPadding/*left*/+theTextView.textContainer.lineFragmentPadding/*right*/), theTextView.frame.size.height-(theTextView.contentInset.top+theTextView.contentInset.bottom+theTextView.textContainerInset.top+theTextView.textContainerInset.bottom+theTextView.textContainer.lineFragmentPadding/*top*//*+theTextView.textContainer.lineFragmentPadding*//*there is no bottom padding*/));
CGSize calculatedSize = [theTextView.text sizeWithFont:theTextView.font
constrainedToSize:textViewContentSize
lineBreakMode:NSLineBreakByWordWrapping];
CGSize adjustedSize = CGSizeMake(ceilf(calculatedSize.width), ceilf(calculatedSize.height));
Inspired by #MrNickBarker's answer, here's my solution:
CGFloat width = 280.0f;
UITextView *t = [[UITextView alloc] init];
[t setFont:[UIFont systemFontOfSize:17]];
[label setText:#"some short or long text, works both"];
CGRect frame = CGRectMake(0, 0, width, 0);
[t setFrame:frame];
// Here's the trick: after applying the 0-frame, the content size is calculated and can be used in a second invocation
frame = CGRectMake(0, 0, width, t.contentSize.height);
[t setFrame:frame];
The only issue remaining for me is that this doesn't work with modified insets.
Still can't believe such twists are required, but since -[NSString sizeWithFont:forWidth:lineBreakMode:] does not respect insets, paddings, margins, line spacings and the like, it seems this is the only working solution at the moment (i.e. iOS 6).