UITextView text becomes invisible after height reaches 8192.0 - ios

I have a non-scrollable UITextView embedded in a UIScrollView and add text to the UITextView dynamically. The UIScrollView adjust it's contentSize accordingly based on the TextView's frame. However, once the UITextView exceeds a height of 8192, the text will become invisible (but still there, because you can use the magnifying glass to highlight text and even see parts of the text through the magnifying glass).
CGRect textviewFrame = self.TextView.frame;
textviewFrame.size.height = [self textViewHeightForAttributedText:self.TextView.attributedText andWidth:320.0];
self.TextView.frame = textviewFrame;
self.ScrollView.contentSize = self.TextView.frame.size;
Helper function to size UITextView accordingly:
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString *)text andWidth:(CGFloat)width
{
UITextView *textView = [[UITextView alloc] init];
[textView setAttributedText:text];
CGSize size = [textView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
Didn't realize it was the same exact problem that was unsolved here until I tested it out explicitly by forcing the max size to 8193 and the problem occurred (while a max size of 8192 still had the text showing correctly). Anyone run into this problem before and know of a work around? Thanks

I was recently hit by this problem and have worked out an effective way around it. Although it seems like an iOS bug IMHO it's really not... there are practical limits to CALayer sizes, plus drawing an 8K high piece of text takes a long time. Much better to do as Apple intended and to only render the bit of text that's visible... that's why UITextView extends UIScrollView after all.
The problem is that UITextView isn't terribly easy to integrate with other bits of UI. In my case I am working on a news app where a single UITextView is used to render the article, plus there's some separate UI (titles and buttons etc) above and below it, all hosted in a single scrollable container.
The solution I've found is to split the UITextView into two views... a dedicated UITextView container whose frame is the full text size (i.e. the same size your UITextView's contentSize) and which is the superview of your UITextView. Your child UITextView's frame should be set to the bounds of the outer scrollable container.
Then all you have to do is use key-value observation to monitor the contentOffset property of your outer scrollable container view (in my case this is a UICollectionView). When its contentOffset changes you update (1) the contentOffset of your UITextView, and (2) the transform property of the UITextView's layer. By updating that transform property the UITextView is fixed to fill the currently-visible part of it's superview. But because you're also updating the UITextView's contentOffset, this trickery is totally invisible to the user... it looks and behaves as if the UITextView is simply very large.

Here's a fully functional solution, for anyone who'd like it!
** Assuming your content size will not exceed the limits of two text views **
This solution works by adding two UITextViews to your view, and splitting your text between them. It looks complicated, but it's actually very simple! I've just written a very verbose description :)
Step 1 - Add two UITextViews to your view:
I added mine in my storyboard. Place them so that one is directly above the other, with no space between them. Don't worry about setting the height (we will set that later).
Set constraints on the views so that they are tied to each other from the top and bottom, and the surrounding edges of the container from all other sides, including your desired padding. i.e. tie the first text view to the container from the top, left, and right, and to the second text view from the bottom. Tie the second text view to the container from the bottom, left, and right, and to the first text view from the top. This will ensure that they stretch appropriately when the content is set.
Don't set any constraints on the height of the views, or if you must (to avoid warnings from the constraints inspector), set the height of one of the views to be >= 20, or some similarly small number.
Disable scrolling, bouncing, and scrolling indicators for both your text views. This solution relies on the views being a fixed, non-scrollable height, so if you'd like your content to scroll, you should use a UIScrollView or UITableViewCell as a container.
Create outlets for your two new text views in your view controller file, naming them something like textView1 and textView2.
Step 2 - Set textContainerInsets to zero:
Set the textContainerInset property on both text views to zero, either using User Defined Runtime Attributes:
or code:
self.textView1.textContainerInset = UIEdgeInsetsZero;
self.textView2.textContainerInset = UIEdgeInsetsZero;
This will ensure that the no visible space will appear between the two views when the content is set, and should not affect the other spacing around your views.
Step 3 - Split your content, set it, and update the view heights:
Simply copy the following code into your view controller file (viewDidLoad), and set the contentString variable to your content.
/* Content is split across two UITextViews to avoid max drawing height */
NSString *contentString = #"Some very long piece of text...";
// Set text
NSArray *components = [contentString componentsSeparatedByString:#"\n"];
NSInteger halfLength = [components count] / 2;
NSArray *firstHalf = [components subarrayWithRange:NSMakeRange(0, halfLength)];
NSArray *secondHalf = [components subarrayWithRange:NSMakeRange(halfLength, [components count] - halfLength)];
NSString *contentString1 = [firstHalf componentsJoinedByString:#"\n"];
NSString *contentString2 = [secondHalf componentsJoinedByString:#"\n"];
self.textView1.text = contentString1;
self.textView2.text = contentString2;
// Set text view heights
CGFloat fixedWidth1 = self.textView1.frame.size.width;
CGFloat fixedWidth2 = self.textView2.frame.size.width;
CGSize newSize1 = [self.textView1 sizeThatFits:CGSizeMake(fixedWidth1, CGFLOAT_MAX)];
CGSize newSize2 = [self.textView2 sizeThatFits:CGSizeMake(fixedWidth2, CGFLOAT_MAX)];
CGRect newFrame1 = self.textView1.frame;
CGRect newFrame2 = self.textView2.frame;
newFrame1.size = CGSizeMake(fmaxf(newSize1.width, fixedWidth1), MIN(newSize1.height, 8192));
newFrame2.size = CGSizeMake(fmaxf(newSize2.width, fixedWidth2), MIN(newSize2.height, 8192));
self.textView1.frame = newFrame1;
self.textView2.frame = newFrame2;
This code splits the contentString roughly in the middle, looking for the nearest newline. If you'd like to split your content on a different character, simply change all occurrences of \n above to whatever you'd like to split on.
Step 4 - Set your container view height:
Set your container view (scrollView, tableViewCell, whatever else) to the height of your two text views, plus whatever additional space you've set above and below them.
CGRect viewFrame = self.myView.frame;
viewFrame.size.height = MIN(self.textView1.frame.size.height, 8192) + MIN(self.textView2.frame.size.height, 8192) + kTextViewContainerPadding;
[self.myView setFrame:viewFrame];
(In my code, kTextViewContainerPadding is a macro I've set to the sum of the space above and below my two text views, within their container).
That's it! Good luck.

Try enabling the scroll for the scrollView.
Keep the height of the textView > height of the content, so that in reality there will be no scroll, but scrollEnabled should be = YES
It solved the problem for me.

Hello I think am not late to answer. I got the same problem like you. This is my solution:
textView.scrollEnabled = YES;
contentTextView.attributedText = finalAttrString;
// contentTextView.text = [attrString string];
contentTextView.font = kFont(contentTextFS + [valueOfStepper intValue]);
[contentTextView sizeToFit];
contentTextView.height += 1;//This is the key code
//contentTextView.height = 8192.0f
Them I solved the trouble and I can change size dynamic.Successfull on iOS 8

Related

UITextView doesn't scroll sometimes

While typing in a UITextView sometimes it scrolls down to current line(case a) but it doesn't the other times(case b).
There's another problem which is:
The same UITextView sometimes show all the text in it (case 1) but other times it doesn't show the last line of text(case 2).
Whenever case 1 happens case a follows.
and Whenever case 2 happens case b follows.
This is the hierarchy of the view:
Size(variable height-fixed width) of these UITextViews as well as UICollectionViewCells are calculated using sizeWithFont:constrainedToSize:lineBreakMode:
Limits of height are set from 43 to 120.
if height>43 then enableScrolling is set to YES, otherwise to NO(Logic X).
Scrolling is enabled when textViewBeginEditing and Logic X is applied when textViewEnded Editing.
There is no scrolling in case 2.
Please suggest cause and workarounds.
On iOS7, I think that you could leave the UITextView's scrollEnabled property set to YES in all cases. If it contains less text it will just not scroll. If you set the property while configuring the cell, you might get this kind of weird behavior, because UIKit is reusing those cells and probably the UITextView too.
For making the last line visible, I'm guessing you need to calculate the text view size more accurately. Try using the attributedText to set the text, with all the formatting you need. Then, in order to calculate the size, you can do it like this:
NSAttributedString *text = self.yourCellsTextView.attributedText;
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:text];
CGSize size = [calculationView sizeThatFits:CGSizeMake(self.yourCellsTextView.frame.size.width, FLT_MAX)];
CGFloat finalMessageHeight = size.height;
Hope this helps :).

Adding multiple subviews to UIScrollView

I've been messing around and trying to make my own messaging application (for practice).
I'm adding a custom UIView to a standard UIScrollView based on an IBAction.
Each time the user presses a button, I do the following (in order):
Enumerate the subviews in the scrollView and increase the value of an
integer. I use this int to set the y-position of the new custom
view
Create a custom UIView with a frame size that varies only in width. The view has a UILabel on top of it that has the text.
Create a new CGSize that I use to set the contentSize of the
UIScrollView. The new CGSize is the width of the scrollView + the
height of the custom view + some padding.
I then set the contentSize of the scrollView.
I then add the custom view as a subview of the scrollView.
I created a timer that adds a new message/subview every two seconds to test multiple messages and the issue I am seeing is that once the subviews are added outside the viewable area of the UIScrollView, they stop being added. They are only added once I bring the messages back into the viewable area, and even so, they are added overlaying at times.
Now, I am aware of a few things: I need to add the subviews on a separate thread since they won't be added while the user is scrolling. I know that I will need to "clean up" subviews as they build up since my app will keep eating memory as more subviews are added. I am also aware that I should reposition the UIScrollView as messages are added since it doesn't make sense to add them outside of the viewable area. Finally, I don't think enumerating the subviews is very elegant... so I will change that later... nor is the temp UILabel I make... Now for some code:
- (void)pushMyMessage:(NSString *)message
{
UILabel *tempLabel = [[UILabel alloc]init];
tempLabel.text = message;
[tempLabel sizeToFit];
//adding the new message as a subview below any previous messages
if (self.messageView.subviews.count > 0)
{
int yIndex = 0;
for (SDMessageView *view in self.messageView.subviews)
{
//increase the y value for the frame of the next message
yIndex += view.frame.size.height;
//add a little padding
yIndex += 5;
}
SDMessageView *newMessage = [[SDMessageView alloc]initWithFrame:CGRectMake(20, yIndex, tempLabel.frame.size.width + 8, tempLabel.frame.size.height) setMessage:message];
CGSize newSize = CGSizeMake(self.messageView.frame.size.width, self.messageView.contentSize.height + newMessage.frame.size.height + 30);
[self.messageView setContentSize:newSize];
[self.messageView addSubview:newMessage];
}
else
{
//initial message
SDMessageView *newMessage = [[SDMessageView alloc]initWithFrame:CGRectMake(20, 20, tempLabel.frame.size.width + 8, tempLabel.frame.size.height) setMessage:message];
CGSize newSize = CGSizeMake(self.messageView.frame.size.width, self.messageView.contentSize.height + newMessage.frame.size.height + 30);
[self.messageView setContentSize:newSize];
[self.messageView addSubview:newMessage];
}
}
Any ideas on why the subviews are only added while in view? Also, any ideas on how I can do this efficiently?
Thanks for any help in advance!
Few things I noticed while reading your post:
You should not make any UI interface changes (like adding subviews) on secondary thread, this should be done on main thread.
Why don't you use UITableView for displaying messages? If you use table view, you don't have to calculate all the frames, content sizes and etc., just need to give correct height to the cell, and this would efficient way of solving what you are trying to achieve.
I didn't really understand, why your subviews are not added to the hidden area of the scroll view, in my practice, I didn't notice such a behaviour of the scroll view (I used it a lot), there should be some other issue with your code (are you adding subviews on secondary thread?).
Hope this was helpful, Good Luck!

How to make UIScrollView occupy screen height when UITableView at the bottom is hidden?

I have a UIView that has two child elements: a UIScrollView on the upper half (which contains two UILabels), and a UITableView at the bottom. This is basically a dictionary and the purpose of the scroll view is to display the word and definition, and the table view for displaying the related words. Not all words in my dictionary have a related words array associated to them, so I hide the UITableView when that array is empty.
However, I can't get the UIScrollView to fill the entire parent view when the UITableView is hidden. Here's what I've tried so far:
- (void)updateUIWithWord:(NSString *)theWord
andDefinition:(NSString *)theDefinition
andRelatedWordsArray:(NSArray *)theRelatedWordsArray {
self.navigationItem.title = theWord;
self.word.text = theWord;
self.definition.text = theDefinition;
self.relatedWordsArray = theRelatedWordsArray;
if (![relatedWordsArray count]) {
relatedWordsTableView.hidden = YES;
// set the UITableView's width and height to 0 just to be sure
// I feel this isn't needed though
CGRect relatedWordsTableViewFrame;
relatedWordsTableViewFrame.size = CGSizeMake(0, 0);
relatedWordsTableView.frame = relatedWordsTableViewFrame;
// then make the scroll view occupy the remaining height;
// that is, the self.view's actual height
CGRect scrollViewFrame;
scrollViewFrame.origin = CGPointMake(0, 0);
scrollViewFrame.size = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
scrollView.frame = scrollViewFrame;
}
}
Simply put, this doesn't work. For any word that has no related words and a very long definition, the scroll view simply occupies the same amount of height even with the table view gone. Help?
ADD: I tried fixing the constraints in the UIScrollView to make it relative to the top of the UITableView instead of having a fixed height, but that doesn't seem possible.
You have an "if" and an "else". Only one of those is going to execute. So when the "if" part runs and relatedWordsTableView.hidden is set to YES, the table view is hidden but nothing else happens. The "else" part isn't running so nothing is happening.
Approached the problem in a different way. I made the UIScrollView occupy the whole screen and put the UITableView inside it, below my two labels, with scrollling disabled. Now I can just hide and show it.

How to create a multi-line UILabel, with a maximum width?

I'm writing a simple IRC client that I'm modeling after Twitter's iOS app appearance. I've taken a screenshot of the Twitter app, for reference:
It looks like a simple table view with a few labels inside of each cell. So, in my app, I am programmatically creating a table and the cell formatting. My custom cell has only two labels in it, which I have positioned one on top of the other. The top label is a simple 1-liner. The bottom label I would like to contain longer messages, and need it to word-wrap to multiple lines while staying within my specified width.
How do I achieve this?
So far, I've tried explicitly setting the frame of the label to the dimensions that I want, but it does not word-wrap, if this is all I do. It just flows out of the cell horizontally. I then tried calling sizeToFit, within the tableView:cellForRowAtIndexPath: function, for this label, but it appears to word-wrap at a very small width - the text wraps after like two or three letters and then flows out of the cell vertically.
I can't seem to figure out how to get the text within the label to wrap after a specified width. Any ideas?
My custom cell class: https://github.com/ryancole/pound-client/blob/master/pound-client/views/MessageListCell.m
The cellForRowAtIndexPath function: https://github.com/ryancole/pound-client/blob/master/pound-client/controllers/MessageListViewController.m#L62-L84
Edit 1:
To demonstrate what happened when I set numberOfLines to 0, for unlimited, I have attached a screenshots of that being called. It wraps after a few characters, instead of first taking up the specified width of the UILabel's frame. This is being set prior to called sizeToFit.
You need to set numberOfLines to the number of lines you want, or 0 which allows for an unlimited number of lines (the default is 1). You might also need to set the lineBreakMode to NSLineBreakByWordWrapping (although that might be the default).
After Edit: If you want the text to start at the top, then I think you'll have to use variable height cells, and not set an explicit size for your custom cell. I did it this way in one of my projects:
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0; // allows label to have as many lines as needed
label.text = _objects[indexPath.row][#"detail2"];
CGSize labelSize = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(300, 300000) lineBreakMode:NSLineBreakByWordWrapping];
CGFloat h = labelSize.height;
return h + 50;
}
The label I create here, is just for calculating the height of the row, it's discarded after this method ends. The width of the cell is determined by the 300 argument I have in the constrainedToSize: parameter. The +50 was just a fudge factor I added to get my cells looking right -- you'd probably want to mess with that number to get what you want. In my custom cell class, I used initWithStyle:reuseIdentifier, and didn't set any size.

ios autolayout similar

I'm pretty new to ios. And while writing my first app i'v encountered that autolayout is only for ios 6.0. And i'd wish to make my app for at least 5.0 ios.
Maybe anyone would know how to make this without autolayout.
I have label which has dynamic text, 1row or 2 rows or 3 rows depends on user settings. And below it i have uitextfield. With autolayout i have no headache as it does all the work, the textfield sits nicely below in 1 2 or 3 rows of text above (resizes, moves automatically).
So how should i do this without autolayout?
Without autolayout you have to handle this in code. The recommended way of doing this would be to subclass your container view (the view that contains your label and text filed) and override the layoutSubviews method. In there you set the frames of the view's subviews manually, based on your desired criteria (e.g., the label text metrics).
EDIT: here's a specific example of something that could be in the containverView layoutSubviews method (typed from the top of my head):
// Those could be IBOutlets, or obtained by inspecting self.subviews
UILabel *label = self.label;
UITextField *textField = self.textField;
// Determine the labelSize, we could limit the maxSize (especially the height) manually here
CGSize maxSize = self.bounds.size;
CGSize labelSize = [label.text sizeWithFont:label.font constrainedToSize:maxSize lineBreakMode:label.lineBreakMode];
// Set the computed label size
CGRect labelFrame = label.frame;
labelFrame.size = labelSize;
label.frame = labelFrame;
// Now move the textField just below the label (we could also add a vertical margin here if we want)
CGRect textFieldFrame = textField.frame;
textFieldFrame.origin.y = labelFrame.origin.y + labelFrame.size.height;
textField.frame = textFieldFrame;
This just makes sure the text field is always below the label. Depending on the constraints you had in place, you might need to add more code to make sure the UI lays out correctly.
You also need to make sure that [containverView setNeedsLayout] gets called when the label text changes.
if you are using nib or storybord you can just remove the mark in the file inspector

Resources