Dynamically resizing a Label within a Scrollview? - kivy

Let's say you have a setup like the following:
ScrollView:
size_hint: (1, 0.5)
Label:
size_hint: (1, None)
Initially, the Label has no content/text. If I understand correctly, when the objects are created, the Label's height is None.
When the app is run, the Label's text property is set to multiple lines of text, which should push the content of the Label outside the boundaries of the ScrollView. However, for scrolling to happen, it seems that the Label's y dimension/height has to be dynamically resized.
What is the best way to dynamically resize the height of the Label in accordance with the Label's new content such that a scrolling action can occur?

The actual text is displayed in a Rectangle whose size is not coupled to the Label size by default. When the amount of text is large, this grows larger and can exceed the Label bounds, but does not resize the label.
The relevant property controlling the real size of the displayed text is Label.text_size. To get the behaviour you want, you can simply do:
ScrollView:
size_hint: (1, 0.5)
Label:
size_hint: (1, None)
height: self.text_size[1]
This binds the height of the label to track the height of the displayed text, and so should give you the behaviour you want.
As a side note, it's often useful to change or watch text_size. For instance, to make the text wrap at the edges of the Label rather than relying on manual newlines, you can set text_size: self.size which means the text will be automatically wrapped if its width would exceed the label width. This is also important if working with halign or valign, which control the text position within its texture and not within the label itself - those properties will have no visible effect unless you manually set text_size to (again) something larger than the space the text actually takes up.
Edit: As per your comments below, here's an example of a Label in one of my apps, which grows vertically if the length of text increases (so the user can scroll), but also automatically wraps text when its width is greater than the label width. It seems I actually did this by putting the Label in a GridLayout, which I vaguely remember was important for some reason.
GridLayout:
cols: 1
spacing: 10
size_hint_y: None
height: thetb.texture_size[1]
Label:
id: thetb
text: 'whatever'
text_size: self.width, None
size_hint: (1, None)
size: self.parent.width, self.texture_size[1]
You can see I use text_size to control the text boundingbox (so it wraps at the label edges), but bind the GridLayout height to really track the texture_size height. The GridLayout is placed in the ScrollView, and I get exactly the behaviour I think you want.

Related

Make an image in Kivy exactly the same size as the widget bounding box

I'm having trouble in Kivy setting an image size. I want to keep the aspect ratio fixed but I also want to position things precisely over the top of the image.
To do this, I make a RelativeLayout that has the same size and position as the image. However, when I check the size of the layout, it is always slightly larger than the displayed image. Making precise alignment very difficult. The wiki mentions something like this:
By default, the image is centered and fits inside the widget bounding box. If you don’t want that, you can set allow_stretch to True and keep_ratio to False.
They also include code if you want to make the image slightly larger than the containing widget:
<-FullImage>:
canvas:
Color:
rgb: (1, 1, 1)
Rectangle:
texture: self.texture
size: self.width + 20, self.height + 20
pos: self.x - 10, self.y - 10
But nothing about making them exactly the same size!? Surely I should be able to dictate the containing widgets size so the aspect ratio etc. is exactly as required.
I have tried many things but whatever I try, I cannot get the outside edges of the displayed image and a layout to coincide.
Does anyone have any experience with this?
Apologies, I have discovered the problem, the images had a transparent border that I wasn't aware of (the images were passed on to me from elsewhere) I have removed this and it has solved the problem.

How can I set the height of a UITextView to exactly match its truncated content?

I have a UITextView whose height I would like to limit to some reasonable value, with the text truncating if necessary. How can I make sure that the height of the text view matches that of the truncated content? If, for example, I set the height to a fixed value, there will some variable space at the bottom of the text view which will affect the layout of items below it.
Is there some way to set a desired height, measure the truncated text, and then use that measurement to more precisely adjust the height? Is there even a way to measure the height of the displayed text within the UITextView?
Edit
I need to clarify:
I do not need help truncating the text. As you can see from the screenshot, the text is truncated already.
I cannot measure the text because I would need to measure the the truncated text, which I don't have access to.
The gap at the bottom of the text view is not related to the textcontainerInset.
This is how the screenshot is currently built:
The text is set to some long string.
The text view is artificially constrained to some height, let's say 300. This produces the truncation.
Because 300 is not a precise multiple of the line height, there is some additional space below the last truncated line.
What I would like to do:
After sizing the text view to 300, measure the precise height of the truncated text so that I can then resize the text view a second time to fit it without the additional spacing (e.g. set it to 285 if that is the measured size).
Or, some other method to achieve the same end result.
I expect it's truncated because of your constraints. So you need to calculate the text size in the code and update the constraint value:
let boundingRect = (textView.text as NSString).boundingRect(
with: maxSize,
options: .usesLineFragmentOrigin,
attributes: [ .font: textView.font ],
context: nil
)
heightConstraint.constant = boundingRect.height.rounded(.up)
The result of this operation is float and not related to pixels. When the view size is calculated by the constraints, it get's rounded to pixels (0.5 or 0.333...), that's why we need to round it by ourself to exclude unpredictable cases. To get pixel perfect height you can round it according to screen scale:
textViewHeightConstraint.constant = (boundingRect.height * UIScreen.main.nativeScale).rounded(.up) / UIScreen.main.nativeScale
Also, as #nghiahoang mentioned below, don't forget to zero the insets
textView.textContainerInset = .zero
Here's my sample project
Compile the solution of Philip and set the inset.bottom to 0
textView.textContainerInset.bottom = 0
Directly, it is not possible. If you want to do it, you have take UILabel behind the UITextView. Because UILabel has the property to auto extend the height and width based on text. Now. set equal constraints of UILabel to UItextview.
what should be constraints, let me explore step by step :
set leading and trailing of UILabel.
Fix top constraints of uILabel.
you not need to set height constraint but if you want, you can set greater than equal to constraint of constant 20 or 30.
Now set leading, trailing,top and height constraints of UITextview equal to UILabel.
Now whatever you set text in UIlabel , give it to label as well. I did not in past. If you will find any issue please ask in comment

Horizontal UIStackView with two label (one multiline label, one one-line)

I have a horizontal UIStackView which has two UILabel in it. First UILabel is multiline (two line) and other one is one line only. They both have default content compression and resistance priorities. My problem is that even there is a gap between labels, "best" word in first text goes second line. I noticed that first label doesn't goes beyond half of total width.
What I want is that second label should always show itself and first label should size It self for remaining space. If It can't fit to one line It should be two line. However, If second label is too short and first label is a long one but both of them can fit, first label should go beyond half of the width.
P.S I need to use UIStackView in this scenario because there are other cases. I know putting two label inside UIView may solve the problem.
UIStackView:
- Distribution: Horizontal
- Alignment: Center
- Spacing: 0
UILabel:
- Number of line: 2
- Line break: Word wrap
UILabel:
- Number of line: 1
View hierarchy:
Desired Result:
OR
EDIT: I calculate the width of second label and give width constraint. I think It solved my problem, I'll test a bit.
//Give specific width to second label to make first one calculate right number of lines.
if let font = UIFont(name: "Metropolis-ExtraBold", size: 15) {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = (secondLabelText as NSString).size(withAttributes: fontAttributes)
secondLabel.widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
To try and simplify...
Forget calculating any widths... what matters is the horizontal Content Hugging and Content Compression Resistance
Leave the left (blue) label at the defaults:
Content Hugging Priority
Horizontal: 251
Content Compression Resistance Priority:
Horizontal: 750
But set the right (orange) label to:
Content Hugging Priority
Horizontal: 1000
Content Compression Resistance Priority:
Horizontal: 1000
Results:
The only other issue would be if the text in the right-side label exceeds the full width of the view -- but you haven't indicated that you might need that much text.
You should set different horizontal content compression resistants for each of them:
For this case, the blue one(multiline) should have something less than the orange one(Singleline).
Assistive note: Multiline: 250, Singleline: 750
Orange Settings:
Blue Settings:
Note that I have set the line limit of the orange one to 0. You can set it to anything you like. But if you like to make it selfsize to anyhight taller view should have higher vertical compression resistant than 250.
First embed your multiline label inside a view and constraint the label to this view (top, leading, trailing, bottom). After that add your view with your multiline label and single line label to your stackView.
Then you can use .fillProportionally for the distribution of your UIStackView and the spacing value to specify the space between your two labels.
Make also sure to use Required (1000) for your horizontal content compression resistance priority on your right number label.
View-hierarchy:
Result:
With spacing between your labels:

Difference between text_size and texture_size in kivy label properties

When setting up properties in kivy label, there are texture_size and text_size. What is the difference between them?
texture_size is a dynamically generated dimension for Label in kivy. It depends on the text length and font size. It is parameter value for the attribute size.
Label:
size: self.texture_size
text_size is an attribute for Label. Here you can hard-code the size of the Label.
Label:
text_size: cm(6), cm(4)

UILabel AutoResize cuts off the top part of the text

I have a UILabel which autoresizes along with its parent view. The label has AdjustsFontSizeToWidth turned on and has a minimum text size of 0 - so basically it tries to fit all the text into whatever size the UILabel is.
The problem I am having is that vertically the text gets cut off. So yes, the label is adjusting its font size to the width of the label but the text is too tall for the label and thus some of the text is getting cut off.
Is there anyway to work around this so that all of the text, the full height and full width are shown?
I attach an image to show what I mean. The red box is the parent view, the purple box is the UILabel.
Thanks for your help.
What you are adjusting automatically is the Width and not the Height. The Height is something you'll have to adjust manually based on the maximum font size you will use. If the maximum (assigned initial) font size fits in height, so will the smaller one's do, after they are automatically adjusted
I suspect that Lefteris is right, that minimum text size focuses on font size for the width of the control. Note, though, if you want it to resize the font to fit, you want a non-zero minFontSize. See minimizeFontSize notes. Also check out the various NSString UIKit Additions that can be used to get the size of the control necessary to fit your text, and programmatically adjust the size (i.e. the frame) your UILabel accordingly.
In my case there was a bogus vertical centering of a view under the labels being clipped and squashed. That somehow took priority over compression resistance priority of 1000 for the labels. No warning on console about conflict though. But the view debugger was of some help.

Resources