I am encountering a strange behaviour when using UITextView with a long text. In particular, when setting the maximumNumberOfLines of its textContainer to 0, the content disappears completely.
I have created a small test project to be sure there was nothing strange in my code that was causing it, and you can see it in the screenshots. In my project I have a UIViewController that contains a UIScrollView, that contains a vertical UIStackView. The stack view contains a UITextView (the red title in the screenshots), another stackview containing a label, text view, button, and then other text views.
When the button is clicked I am setting maximumNumberOfLines to 0 (before it was 2), and the content just disappears. I've tried with and without animation and I have the same result.
The disappearing seems to be related to the height of the final text, as if I use a smaller font, the content disappears only after setting much more text.
Any ideas why it could be happening?
For completeness, I am using Xamarin.iOS, and here
is my ViewController.
The Content disappears because your text is too large for a UIView object to show. According to this post we know that, actually UIView has a maximum height and width limited by the amount of memory they consume.
In my test if we don't set too much characters to the textView(remove some textView.Text += textView.Text;), the content will show. And I also tested that on UILabel, so does it. Because they all inherit from UIView.
If you do want to show so many strings, try to enable the textView's ScrollEnabled. Do not let the textView's Bounds exceed the maximum limit. You can try to add the Height and Width Constraints when you want to expand the textView:
var textViewContraints = new NSLayoutConstraint[]
{
NSLayoutConstraint.Create(textView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1f, 700),
NSLayoutConstraint.Create(textView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1f, 500)
};
UIView.AnimateNotify(1.2d, () =>
{
if (!buttonExpanded)
{
textView.ScrollEnabled = true;
textView.TextContainer.MaximumNumberOfLines = 0;
textView.TextContainer.LineBreakMode = UILineBreakMode.WordWrap;
textView.SizeToFit();
textView.AddConstraints(textViewContraints);
expandButton.Transform = CGAffineTransform.MakeRotation((nfloat)(Math.PI / 2.0f));
textView.Text = "r" + textStr;
textView.Text = textView.Text.Substring(1);
}
else
{
textView.ScrollEnabled = false;
foreach (NSLayoutConstraint constraint in textView.Constraints)
{
textView.RemoveConstraint(constraint);
}
textView.TextContainer.MaximumNumberOfLines = 3;
textView.TextContainer.LineBreakMode = UILineBreakMode.WordWrap;
expandButton.Transform = CGAffineTransform.MakeRotation(0f);
textView.Text = textStr;
}
scrollView.LayoutIfNeeded();
buttonExpanded = !buttonExpanded;
}, null);
Related
I have a UITextView and initially I want to set it to have 4 lines, however when the user clicks a read more button, I want to expand it to full length, I assume this is either through getting maximumNumberOfLines to 0 or to a high number, say 30
Issue is, after changing lines from 4 to 0 (or 30) it doesn't relayout the uitextview to be its full height, it seems capped at 4 lines.
I call self.setNeedsLayout() and self.layoutIfNeeded() to trigger layout, but it won't revert to its full height
I have also tried calling descriptionTextView.invalidateIntrinsicContentSize() after changing the line count with no luck
What am I doing wrong here?
Thanks
You may be doing a couple things wrong...
First, in order to "auto-size" the height of the textView, it must have scrolling disabled.
Second, it cannot have a fixed height (neither a height constraint not top & bottom constraints).
Edit: For clarification... When I say "no bottom constraint" that doesn't mean it cannot have a bottom constraint. Rather, the bottom constraint cannot be set in a way that would prevent the textView's height from changing. So, for example, if the textView is in a table view cell, a bottom constraint is fine, as long as the cell is designed and used in a way that the height of the textView controls (or contributes to) the height of the cell.
This is a simple example that will toggle the textView between 4-lines and Zero-lines (showing all the text content):
class ExpandingTextViewViewController: UIViewController {
let descriptionTextView: UITextView = {
let v = UITextView()
v.translatesAutoresizingMaskIntoConstraints = false
// disable scrolling
v.isScrollEnabled = false
// give it a background color to make it easy to see the frame
v.backgroundColor = .yellow
return v
}()
let theButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .red
v.setTitle("Toggle TextView", for: .normal)
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(theButton)
view.addSubview(descriptionTextView)
NSLayoutConstraint.activate([
// button 40-pts from the top, centered horizontally
theButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
// textView 40-pts from bottom of button, 20-pts padding left and right
// NO height or bottom constraint
descriptionTextView.topAnchor.constraint(equalTo: theButton.bottomAnchor, constant: 40.0),
descriptionTextView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
descriptionTextView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
])
// give the textView some sample text
descriptionTextView.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
// start with max number of lines set to 4
descriptionTextView.textContainer.maximumNumberOfLines = 4
theButton.addTarget(self, action: #selector(toggleTextView), for: .touchUpInside)
}
#objc func toggleTextView() -> Void {
// toggle max number of lines between 4 and Zero
descriptionTextView.textContainer.maximumNumberOfLines =
(descriptionTextView.textContainer.maximumNumberOfLines == 4) ? 0 : 4
// tell auto-layout abour the change
descriptionTextView.invalidateIntrinsicContentSize()
}
}
Results:
Of course, you'll need to add some code to handle a case where your textView has so much text it will extend beyond the bottom of the screen (or outside the bounds of its superview) -- either by checking the resulting height, adjusting it and toggling scrolling, or embedding the textView in a UIScrollView (for example).
Changing the number of lines on your textField just affects the number of lines your textField is allowed to use to display its text. Setting maximumNumberOfLines to 0 crams the text all on one "line" and truncates it at the end of its width - so it doesn't really hide the remaining text in that sense.
Instead of changing maximumNumberOfLines, you'd be best off letting the text fill the number of lines that is natural for it, and animating the UITextView's heightAnchor instead.
I cannot wrap and align at the same time an UILabel displayed in a UITableViewCell.
I want some UILabels (displayed below with a white background) to be right aligned
and word wrapped if the text is too long. To clarify the sreenshots below:
UILabel with a white background are the labels I am talking about
I am using two different types of cell (respectively with blue and orange background)
The UITableView has a something-like-pink background
The ViewController in which the UITableView is displayed has a light gray background
Either is the alignment correct but the text is not wrapped (Actually the text "Long.. " is long, please see the second screenshot)
Or the text is correctly wrapped but it is not right aligned:
My code is based on this tutorial: How to build a Table View with multiple cell types
Inside the code for the cell displayed with an orange background:
class AttributeCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel?
#IBOutlet weak var valueLabel: UILabel?
let orange = UIColor(red: 1, green: 165/255, blue: 0, alpha: 1)
var item: AttributeLabelLabel? {
didSet {
titleLabel?.backgroundColor = .white
titleLabel?.setLabel(contentText: (item?.attributeNameFromLocalizable)!, alignmentText: .right)
valueLabel?.backgroundColor = .green
valueLabel?.setLabel(contentText: (item?.attributeValue)!, alignmentText: .left)
self.backgroundColor = orange
}
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
}
I added an extension to UILabel to set the alignment and text of the two labels displayed on cell, the way how the text should be wrapped is the same for all labels.
With the extension below the label is aligned but not wrapped (see first screenshot above).
extension UILabel{
func setLabel(contentText: String, alignmentText: NSTextAlignment){
self.text = contentText
self.textAlignment = alignmentText
self.numberOfLines = 0
self.lineBreakMode = .byWordWrapping // inefficient alone
}
}
If I want to have the text to be wrapped then I have to add a call to sizeToFit() but then short label (see label with the text "Short") is not right aligned (see second screenshot above).
extension UILabel{
func setLabel(contentText: String, alignmentText: NSTextAlignment){
self.text = contentText
self.textAlignment = alignmentText
self.numberOfLines = 0
self.lineBreakMode = .byWordWrapping
self.sizeToFit() // allow text to be effectivly wrapped
}
}
Why do I need to specify self.sizeToFit() on the documentation I have found only the use of lineBreakMode is mentionned to wrap a text ?
As I can not handle word wrapping and text alignement, I had the idea to compare the width of the UILabel with its text, and depending on the comparaison handling the alignment (for a text short enough) or the wrapping (if the text is too long). But I did not find how to get the UILabelĀ“s width.
Another idea would be to create a custom UILabel and set all constraint, compression and resistance in code. For now there are no constraints:
Has someone already dealt with such problems?
Is it possible to handle text wrapping and text alignement at the same time ?
Note:
On the second screenshot the UILabel with a wrapped text overlapped the cell boundaries. It is not the first problem and I can live with that for now but if someone has an hint about that...
I actually use the following code to deal with cell with different heights:
cell?.estimatedRowHeight = 200
cell?.rowHeight = UITableView.automaticDimension
You are missing a few constraints.
To get a multiline label to wrap, it must have its width limited (how else would it know the text is too long?).
To get auto layout to adjust the cell's height, you need constraints on the content of the cell to "push down" the bottom of the cell.
So...
Constrain your top-left label to Leading: 0, Top: 0, Width: 77 (I'm using 77 as the width, based on your images).
Constrain your top-right label to Leading: 8 (to top-left label's trailing), Top: 0, Trailing: 0
Constrain your bottom-left label to Leading: 0, Top: 8 (to top-left label's bottom), Width: 77 (or, width equal to top-left label)
Constrain your bottom-right label to Leading: 8 (to bottom-left label's trailing), Top: 8 (to top-right label's bottom, or Top: 0 to top of bottom-left label), Trailing: 0
then, add Bottom constraints of >= 0 to each of the bottom labels.
I'm guessing either bottom label may wrap to multiple lines, so set each one to Number of Lines: 0
The layout:
the result:
A UILabel can definitely be right-aligned and wrap on multiple lines. Here is an example:
Actually, the label content is misleading as it wraps on four lines! ;-)
This can be achieved through AutoLayout constraints and the right settings on the UILabel. This particular UILabel is constrained as follows:
Vertically centred
Leading edge to super view
Width
Here are the constraints, as shown in Interface Builder:
Finally, to have a UILabel line-wrap to multiple lines, its numberOfLines property needs to be set to 0, either through Interface Builder or code.
You can also right-align the text using the textAlignment property, setting it to .right, again through Interface Builder or code.
Hello it's been imposible to center a UIStackView inside a ScrollView having scroll working ...
I've tried lot of things but the best one has been this:
stackView = new UIStackView
{
Axis = UILayoutConstraintAxis.Vertical,
Alignment = UIStackViewAlignment.Center,
};
scroll = new UIScrollView() { };
Add(scroll);
scroll.AddSubview(stackView);
scroll.EnableAutoLayout();
scroll.FullSizeOf(View);
stackView.AddArrangedSubview(logo);
stackView.AddArrangedSubview(_activityIndicatorView);
//... more items
logo.HeightAnchor.ConstraintEqualTo(72).Active = true;
enterpriseCodeField.HeightAnchor.ConstraintEqualTo(50).Active = true;
enterpriseCodeField.WidthAnchor.ConstraintEqualTo(0.65f * View.Frame.Size.Width).Active = true;
//... more items
stackView.SetCustomSpacing(35, logo);
stackView.SetCustomSpacing(35, _activityIndicatorView);
//... more items
View.AddConstraint(NSLayoutConstraint.Create(stackView, NSLayoutAttribute.CenterY, 0, scroll, NSLayoutAttribute.CenterY, 1, 0));
View.AddConstraint(NSLayoutConstraint.Create(stackView, NSLayoutAttribute.CenterX, 0, scroll, NSLayoutAttribute.CenterX, 1, 0));
This method scroll.EnableAutoLayout(); is to: TranslatesAutoresizingMaskIntoConstraints = false;
This method scroll.FullSizeOf(View); is to : set leading trailing top etc...
Anyone can help me please ? what I'm doing wrong ? In android it's very easy to achieve this behavior ...
Thank you so much.
You can use the scrollview centerX anchor constraint. This way you set the centerX anchor constraint of the scroll view to be the same as the scrollView.
[Updated link] I uploaded a project with some code achieving what you are looking for.
Please check it out here -> ScrollViewTest
The link should work now
effectThe requirement is to intercept the display of the string 3/2 of the screen width over the display...
Hope to get your help, thank you!
if (meetModel.title.length > 18) {
title = [NSString stringWithFormat:#"%#...", [meetModel.title substringToIndex:18]];
} else {
title = meetModel.title;
}
This method does not meet my needs. The page display is in Chinese.
I didn't clearly understand your question, but I think you need to add something like this (the example is in Swift 3.1). The idea is that you should set the width constraint, and if the text larger than this width, it should be automatically truncated.
yourLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.66).isActive = true
// It's needed for adding three dots in the end, if the string is larger then it should be
yourLabel.adjustsFontSizeToFitWidth = false
yourLabel.lineBreakMode = .byTruncatingTail
I'm trying to simulate a kind of "Please wait..." UILabel. The label's text must be regularly updated. So far everything works as expected. However, I need to get the intrinsic content height of the label to be able to position its container view (UIView).
The label is the one with the red background, whereas the one with the white background is its container.
I've tried a few different approaches, unfortunately, all in vain. Any help would be greatly appreciated.
private func createBusyLabel(labelText: String) -> CGFloat {
self.busyViewContainer.addSubview(self.busyLabel)
self.busyLabel.backgroundColor = UIColor.red
self.busyLabel.numberOfLines = 0
self.busyLabel.lineBreakMode = NSLineBreakMode.byWordWrapping
self.busyLabel.sizeToFit()
//set the constraints, but skip height constraints
self.busyLabel.translatesAutoresizingMaskIntoConstraints = false
self.busyLabel.horizontalLeft(toItem: self.busyViewContainer, constant: 60)
self.busyLabel.horizontalRight(toItem: self.busyViewContainer, constant: -10)
self.busyLabel.topConstraints(toItem: self.busyViewContainer, constant: 10)
self.busyLabel.text = labelText
//calculate height with margin
let height: CGFloat = self.busyLabel.intrinsicContentSize.height + 20
return height
}
Also, the line counting function, from a previously asked and already answered question, delivers only 1
Here is what it look like after I set the bottom constraint:
A million Thanks to ozgur, who changed my approach. Ozgur, your code works perfect, but unfortunately not for me, as I faced problems with bottomLayoutGuide part. The reason for this is that the label and its container are created in an external class.
Previously I tried to set bottom constraint to the label, which did not return the expected result. However, inspired by ozgur's answer, this time I simply set the bottom constraint to its container and not the label, giving in expected result, like following:
self.busyViewContainer.bottomConstraints(toItem: self.busyLabel, constant: 10)
Thanks to all who put in their precious efforts.
private func createBusyLabel(labelText: String) -> Void {
self.busyLabel.text = labelText
self.busyLabel.font = UIFont.getGlobalFont(size: _textSizeSmall, type: "bold")
self.busyLabel.backgroundColor = UIColor.red
// handle multiline problem
self.busyLabel.numberOfLines = 0
self.busyLabel.lineBreakMode = NSLineBreakMode.byWordWrapping
self.busyLabel.sizeToFit()
self.busyViewContainer.addSubview(self.busyLabel)
self.busyLabel.translatesAutoresizingMaskIntoConstraints = false
self.busyLabel.horizontalLeft(toItem: self.busyViewContainer, constant: 60)
self.busyLabel.horizontalRight(toItem: self.busyViewContainer, constant: -10)
self.busyLabel.topConstraints(toItem: self.busyViewContainer, constant: 10)
// the following line made the difference
self.busyViewContainer.bottomConstraints(toItem: self.busyLabel, constant: 10)
}
The simple fix is adding the bottom constraint between 'self.busyViewContainer' and its superview.
Following your code and syntax it can be something like this:
self.busyLabel.bottomConstraints(toItem: self.busyViewContainer, constant: 10)
It is a common problem with 'Unsatisfiable Constraints'. The autolayout should ensure it satisfies horizontal axis layout so it needs to have both top and bottom constraints in this case.
Apple doc - Unsatisfiable Constraints
Apple doc - Logical Errors
UPD: In this case, the layout of the superview can define a height with intrinsicContentSize:
var intrinsicContentSize: CGSize { return ... }
Where the height of the parent view will be calculated based on the label one.