Auto layout constraints not taking effect on child controls until parent container is resized (swift) - swift

I have a program with an NSSplitView (containing two panes) filling the window. The left pane is an NSStackView and it contains three NSBoxes. The top two each contain an NSTextField for displaying a string of weather information that can change length every time it is updated by code in the background. I have added the relevant constraints to the top two NSBoxes (and their child NSTextFields) to make them expand or shrink to fit the size of the text fields inside them as the text changes length. The third NSBox fills up the remaining space in the stack view.
The problem is that when the window loads, the NSBoxes (as expected) display the correct size as designed in interface builder to fit the default string in the text fields. However, when the update code kicks in and changes the text of the text fields to the downloaded data, which is longer than the default string, the NSBoxes do not adjust their height to fit the larger text. They only change their height when the splitter of the split view is moved. This is annoying because I have to move the splitter every time the number of lines in the text fields changes. Why is this happening and how can I make the boxes update their height to fit the text when it is longer or shorter than before?
It is much like this question (the only source of information I found on my problem): NSScrollView with auto layout not resizing until first manual window resize, however the solution did not work for me.
Below is an image of the interface (again, the top two boxes should resize to fit their text but this only happens when the splitter is moved). It shows longer text than the boxes can display and they haven't resized:

One option is to create a subclass of NSBox and override the intrinsicContentSize property. In your implementation you'd measure the height of the text contained in the box's text field, take account of any vertical padding that exists between the vertical edges of the text field and the corresponding edges of the box, and return the result of your calculation as the height part of the NSSize return value. Then, any time you want the boxes to resize, you can call invalidateIntrinsicContentSize.
I created a sample app to see if I could get this to work which I've posted here. There are a couple of tricky parts which you should be aware of:
Calculating Text Height
The most involved bit of coding in this approach is calculating the height of the text. Fortunately, Apple has documentation that tells you exactly how to do it; here's how that looks in Swift:
// Properties of your NSBox subclass
var layoutManager: NSLayoutManager {
return textStorage.layoutManagers.first! as! NSLayoutManager
}
var textContainer: NSTextContainer {
return layoutManager.textContainers.first! as! NSTextContainer
}
var typesetter: NSTypesetter {
return layoutManager.typesetter
}
// The textStorage object lies at the heart of the text stack. Create this
// object first, the rest follows.
lazy var textStorage: NSTextStorage! = {
var initialString: String = ""
var ts = NSTextStorage(attributedString: self.textField.attributedStringValue)
var lm = NSLayoutManager()
lm.typesetterBehavior = NSTypesetterBehavior.Behavior_10_2_WithCompatibility
var tc = NSTextContainer()
lm.addTextContainer(tc)
ts.addLayoutManager(lm)
return ts
}()
Setting the hugging-resisting priorities for your NSBox subclass
To get the demo working correctly I found that I needed to set the vertical hugging priority and vertical compression resistance values of the NSBox objects to 900.

Related

Expanding UITextView inside a Stack View with scrolling enabled

I am trying to achieve something with Auto Layout and Stackview. I have a Vertical Stackview with a UIView, UITextView, and UIView in it as shown below.
I have checked out the previous answers here but couldn't find a clean solution to achieve this.
The UITextView is editable and must expand as the user types in it - for this I have disabled the scroll for the UITextView in the IB. I also have a height constraint set on the UITextView set to "Greater than or equal to" 10 and number of lines set to 0, so that the UITextView takes the intrinsic height at run time while the user types. I also want the expansion of the UITextView to continue until the UIStackView bottom edge reaches the Keypad accessory's top edge. I have been somewhat able to achieve the expanding UITextView part with my stack view pinned to the top, trailing, and leading edges of the Superview but I keep getting an error that the stackview needs Y position or height which is understandable but if I give it a fixed height or bottom constraint then the UITextView simply won't expand.
I also am not sure how to stop the expansion of the UITextView when it reaches the top edge of the Keyboard accessory view.
The code that I have so far is
let allowedHeight = self.view.frame.height - (self.keyboardHeight! + self.accessory.frame.height)
Basically find the allowed height in the view frame by subtracting the keyboardheight+Accessory view height. This calculation happens correctly. Next I do this (inside textViewDidChange) in hopes that just enabling/disabling the scroll on the UITextView would work but it clearly doens't since the whole text view then weirdly jumps up and down with every key stroke.
func textViewDidChange(_ textView: UITextView) {
if stackView.frame.height >= allowedHeight{
textView.isScrollEnabled = true
}
if stackView.frame.height < allowedHeight{
textView.isScrollEnabled = false
}
}
What is the best way to achieve what I am looking to do?
Couple notes...
I think you'll be better off setting the Max Height of your UITextView rather than of your UIStackView. The Text View will expand / contract based on its content, so you can set a <= height constraint.
Calculate the "elementsHeight" - the vertical size used by your top and bottom views, plus any spacing - and set the "max text view height" to the view height minus elementsHeight ... and subtract keyboard height when visible.
Update the text view's height constraint constant when "max text view height" changes.
Then set up an Observer for the text view's .contentSize ... and enable / disable scrolling based on .contentSize.height compared to "max text view height".
Needs a bit of hoop-jumping, to make sure you update sizes when subviews are laid-out, and to make sure you don't get in a recursion loop.
This is how I set it up:
The initial height constraint on the text view is <= 40 - but that doesn't really matter, as it will be changed via code every time the views layout differently.
I put up an example on GitHub -- it works, but is really just a starting-point. Take a look if you're interested, and pick it apart. https://github.com/DonMag/ExpandingTextView

Unity Resizing Dropdown Dynamically

I have a dropdown list on a filter panel which needs to stretch dynamically to fit the content as shown:
I have tried several content size fitters but cannot find anything, If possible I would like to set a max width it can expand to then truncate everything longer than that, I would also like it to expand only to the right with a right pivot point. I have found a similar example here: https://forum.unity3d.com/threads/resize-standard-dropdown-to-fit-content-width.400502/
Thanks!
Well. Lets start with the code from that Unity forum thread.
var widest = 0f;
foreach (var item in _inputMines.GetComponentsInChildren<Text>()) {
widest = Mathf.Max(item.preferredWidth, widest);
}
_inputMines.GetComponent<LayoutElement>().preferredWidth = widest + 40;
We want to have a max-width allowed, so any content longer than this will be truncated. So let's add that variable:
var maxWidth = 250f;
//or whatever value; you may wish this to be a property so you can edit it in the inspector
var widest = 0f;
foreach (var item in _inputMines.GetComponentsInChildren<Text>()) {
Now we use the smaller of the two width values. Then apply it to the content layout:
}
widest = Mathf.Min(maxWidth, widest);
_inputMines.GetComponent<LayoutElement>().preferredWidth = widest + 40;
The +40 should be retained because that deals with the scrollbar. Your maxWidth value should be chosen to account for this.
Finally we want the longer items to get cut off nicely.
Give each dropdown item a Rect Mask 2D component (Component -> UI -> Rect Mask 2D). The template object exists in the scene hierarchy before the game is run, just disabled. You can just add the component to the parent transform, the one with the image (so the text is clipped).
You'll need to make sure that the mask covers the same width as the image graphic and expands along with it, possibly slightly shorter on the X direction so the text gets cut off before the image border is drawn. This should happen automatically, but you will need to check and possibly make some alterations to the template object. Alternatively you can use an Image Mask, but you'll have to play with that one yourself, but it will allow for non-rectangular clipping.
That's it!

Text in textview is not vertically centered

I'm trying to create a text view which height follows number of line in it. I've worked on constraint but here I've a curious issue :
At the beginning all is centered as I want
When there is a break, text is not vertically centered anymore and goes in top left corner
When you delete characters and then come back to previous line, the text is yet vertically centered
Here is the code
func textViewDidChange(textView: UITextView) { //Handle the text changes here
if(textView.text != ""){
self.animateSendButton(true)
}else{
self.animateSendButton(false)
}//the textView parameter is the textView where text was changed
heightTextfieldConstraint.constant = self.textField.contentSize.height + 2
textField.contentInset.top = 1
}
I don't understand why there are these 3 different cases, do you have any idea to solve it ?
You can try and add an inset to the top, bot, left and right, that should keep the text centered even after a second line appears.
textView.textContainerInset = UIEdgeInsetsMake(5, 5, 5, 5)
You might be better off keeping the text view itself vertically centered while ensuring that the text view is always sized to fit the content. You can do this with Auto Layout.
Set the text view to be vertically centered in its container and also add a height constraint to the text view with a low constant value (about the height of 1 line of text). Then set the text view's Content Compression Resistant Priority to a value that is higher than the text view's height constraint (see screenshots).
I've done it in "Main.storyboard" of this demo project of mine if you want to see a working example:
https://github.com/patricklynch/Walkthrough
Perhaps you could call view.layoutIfNeeded() after text has been entered.

Dynamically set UIScrollView height and UITableView Height depending on content

Setup: I have a scrollview with a label, uiimage, and a tableview (grouped) nested within. The label, uiimage, and tableveiw are populated by a webservice. The last section in the grouped table view contains text that will never be the same and could be as long as 5 characters to 250+. Also, this view is pushed from a basic tableview (so it has a navigation bar. If that matters at all).
Expected: The tableview should extend in height depending on the length of the content received from the web service. Then set the scrollview height to accommodate the height of the tableview
Problem: I'm not quite sure how to approach the issue. I really only know how to change the height to fixed values, which will not work properly in many scenarios.
The width and height of the cell are ignored; the cell cell is re-sized according to the value you return from -tableView:heightForRowAtIndexPath: or (failing that) tableView.rowHeight. It might appear as if the cell is big enough if the label in the cell is sized to be big enough, because the label is allowed to be bigger than (and render outside) the cell.
One way is to override -tableView:heightForRowAtIndexPath: to return the correct height. This isn't really the intended use of UITableView (it's primarily designed for lots of rows of a single height, dynamically generated from a list of content).
Another way is to set tableView.tableFooterView = myCustomFooter instead. This is probably the easiest way. Size it correctly before performing the assignment (the height matters; the table view will set the width for you anyway). Also make sure that the autoresizing flags are not set, or the size will appear to randomly change when the table view changes size (e.g. on autorotation).
I'd just focus on the variable height element, figure out the height that fits the text there. To figure out the height that a string will take when rendered use a snippet like this:
CGFloat MY_TABLECELL_WIDTH = 320;
CGFloat MY_MAX_HEIGHT = 10000;
UIFont *MY_FONT = nil; // load the correct font here.
CGSize maxSize = CGSizeMake(MY_TABLECELL_WIDTH,MY_MAX_HEIGHT);
CGSize textSize = [serverString sizeWithFont:MY_FONT
constrainedToSize:maxSize
lineBreakMode:UILineBreakModeWordWrap];

Multiple UILabel(s) repositioning according to wordwrap

I have this interface with multiple UILabels.
On view loading i populate white labelled values with some data from a db.
The problem is, some of that fields are potentially too long for the interface, so i'd like to compute the total height of one label once the text is word wrapped and reposition the 2 labels below (shifting the Y coordinate) accordingly to the previous label's height.
All of this should go inside a UIScrollView to let the user scroll those labels vertically.
Any chance i can do this easily with some control i still don't know, or do i have to do it manually?
Thanks
You'll need to use the NSString UIKit Additions to compute the height you need to set on your UILabel, and then adjust the other controls appropriately.
Specifically, I think you want to use sizeWithFont:forWidth:lineBreakMode: to get the rect for your UILabel.
Alternatively, you could use a UIWebView and display the information as HTML. I don't know if it's necessarily less work, but you'll get a layout that automatically adjusts to the size of its contents.