I am trying to get a UILabel to flow to multiple lines and push the elements below it downwards using autolayout.
Here is my code called in viewwillappear.
self.myLabel.numberOfLines = 0;
self.myLabel.lineBreakMode = NSLineBreakByWordWrapping;
[_myLabel setContentCompressionResistancePriority:1000
forAxis:UILayoutConstraintAxisVertical];
[self.myLabel setNeedsDisplay];
[self.myLabel layoutIfNeeded];
[self.view setNeedsDisplay];
[self.view layoutIfNeeded];
The UILabel is constrained at the top to the content view and the image below it is constrained to the bottom of the UILabel. The label also has a height constraint of >=21.
On initial load, the label only shows one line.
The weird thing is that after launching a modal VC and the closing it, the label does go to multiple lines but fails to push down the elements below it.
On initial load.
After launching and canceling modal VC
I am wondering if the problem has something to do with timing of laying out subviews but have tried almost every thing.
Thanks in advance for any suggestions.
Simple example:
Create a new ViewController.
Add a button, constrain 40-pts from the top and centered horizontally
Add a UIView (green view). Constrain centered horizontally, width of 240, and top-space to button of 20.
Add two labels to the green view.
Top label, number of lines = 0, leading and trailing constraints of 16, top constraint of 8 (to green superview)
Bottom label, centered horizontally, top space to Top label of 8, bottom constraint of 8 (to green superview).
Connect the Top label to #IBOutlet var multiLineLabel: UILabel! in the view controller.
Connect the button touch-up-inside to the #IBAction func didTap(_ sender: Any) function in the view controller.
NO CHANGES to priorities.
Run the app. Each tap of the button will add text to the top / multi-line label, and it will "push down" the bottom label, which will, in turn, "push down" the bottom edge of the green view.
class ExpandingLabelViewController: UIViewController {
#IBOutlet var multiLineLabel: UILabel!
#IBAction func didTap(_ sender: Any) {
if let s = multiLineLabel.text {
multiLineLabel.text = s + " Here is some more text."
}
}
}
Once you have that working, you can add other elements to the view. Just make sure you have a "chain" of vertical spacing constraints, with the top-most element constrained to the top of the green superview, and the bottom-most element constrained to the bottom of the green superview.
Related
Xcode 10, Swift 5 (this should be doable purely in the Storyboard)
The current layout:
- ChildView (child View of very first default view)
- ButtonView
- ScrollView
- VerticalStackView
- Button
- Button
- ...
- FooterView
- HorizontalStackView
- Button A
- Button B
What contraints do I need to always keep the Footer View at the bottom and make the UIStackView scroll behind it, while still maintaining a fixed height for each button in the UIStackView?
This setup seems to attract multiple problems, from the buttons inside not being clickable (or UITextFields that can't be interacted with), to the UIScrollView not being scrollable or a "Content Size Ambiguity" error (described here) - just because a single constraint isn't set properly.
The result:
If there are less buttons that can all be displayed at the same time, it won't be scrollable but instead simply display the black background below the last button.
How to get there:
The constraints are set using the "add new constraints" button below the preview window.
ChildView:
Trailing/Leading/Bottom: 0 to Superview
Equal Height: to Safe Area:
Control-drag from the ChildView to the Safe Area and pick "Equal Heights"
1. FooterView:
Trailing/Leading/Bottom: 0 to Superview
Height: Equals 50
Top to ButtonView:
Control-drag from the FooterView to the ButtonView
Choose "Top"
Click on the new constraint to open it in the inspector (FooterView.Top equals ButtonView.Bottom)
2. ButtonView:
Trailing/Leading/Top: 0 to Superview
Bottom to FooterView (already explained above)
2.1. ScrollView (black):
Trailing/Leading/Bottom/Top: 0 to Superview
The Bottom/Top constraints keep the ScrollView from "spilling over"
2.1.1. VerticalStackView:
Alignment/Distribution: Fill
Trailing/Leading/Bottom/Top: 0 to Superview
Equal width to ButtonView:
Control-drag from the VerticalStackView to the ButtonView and pick "Equal Widths"
This disables the horizontal scrollbar of the ScrollView
2.1.1.1. Button (gray):
Height: 50
The VerticalStackView takes care of the rest
Of course you can also replace the buttons in the 'UIScrollView' with views to create some type of form.
I'm setting up content in a scroll view with autolayout. The objects in the scrollview are pinned top-to-bottom to the previous one, so that they are under one another. I have a footer view that is added at the end, below these objects.
Here's the catch: when there's few content, the contentView will be smaller than the screen height, so the footer view will appear somewhere in the middle of the screen (which is the normal behavior). But I'd like to prevent that, and make the view stay somewhere at the bottom.
In other words, I would like to setup a double constraint like:
Put this view below all the objects in the scrollview
AND
keep this view at a distance of max [some number] of the bottom of the screen
In a way that both constraints are always satisfied:
If the height of the content is bigger than the screen, then the view appears at the bottom, after scrolling down
If the height is smaller, then the view is "pinned" to the bottom of the screen, leaving a space relatively big between the bottom of the content and the top of this view
How can I achieve that with AutoLayout?
Fairly easy to do with Auto-Layout only... no code required.
The key is to use a "content view" to hold the elements, and a greater-than-or-equal constraint between your "bottom" element and your "footer" view.
In this image, yellow is the main view, green is the scroll view, blue is the content view, the labels are gray and the footer view is pink.
Start with a fresh view controller
add a scroll view, normal constraints (I used 20 all the way around, so we can see the frame)
add a UIView to the scrollView - this will be our "content view"
constrain contentView Top/Bottom/Leading/Trailing all equal to 0 to the scrollView
constrain both the Width and Height of the contentView equal to the scrollView
add your elements - here I used 3 labels
constrain the labels as usual... I used:
LabelA - Top/Leading/Trailing all at 20, vertical spacing to LabelB of 60
LabelB - Leading/Trailing at 20, vertical spacing to LabelC of 60
LabelC - Leading/Trailing at 20
LabelC is also set to Number of Lines: 0 so it will expand with multiple lines of text
Add a UIView as a "footer view" (I stuck a label in it)
constrain the footerView Leading/Trailing/Bottom all at 20 (so we can see the frame)
either set a Height constraint on footerView, or use its content to constrain its height
add a Vertical Spacing constraint from LabelC to footerView, and set it to >= 40
last step, change the Height constraint of contentView to Priority: 250
Now, as you expand/contract the height of LabelC, the footerView will keep at least 40-pts of vertical space. When LabelC gets big enough to "push" footerView below the bottom, scrollView will become scrollable.
Results:
you need to check ContentSize of scrollView and modify FooterView Top Constraint with the required Value
My class code
import UIKit
class scrollViewDrag: UIViewController
{
/// ScrollView Outlet
#IBOutlet weak var mainScrollView: UIScrollView!
/// Footer View top spacing constraint
#IBOutlet weak var footerViewTopConstraint: NSLayoutConstraint!
/// Used for ScrollView Height
var screenHeight = CGFloat()
/// Did Load
override func viewDidLoad() {
super.viewDidLoad()
}
/// Function used to check for height
func checkForHeight(){
/// Get scrollView Height
screenHeight = mainScrollView.frame.size.height
/// Check contentSize Height ?
if mainScrollView.contentSize.height >= screenHeight {
/// When ScrollView is having height greater than your scrollView Height
/// Footer will scroll along other Views
}
else{
/// Issue Case
let spacingValue = screenHeight-mainScrollView.contentSize.height
footerViewTopConstraint.constant = spacingValue
}
}
/// Call the height function in DidAppear
override func viewDidAppear(_ animated: Bool) {
checkForHeight()
}
}
Storyboard
I had used Four View with Equal Heights And at last a footerView is attached as Fourth View
FooterView Top Constraint
Top constraint used as footerViewTopConstraint
Output
Case 1 - Size is greater than scrollView Height
Case 2 - Expected Output
I have button and a label in my table view cell placing one after other vertically.
In my xib design i placed my button then below it placed my label.
if the string for label is empty then i want to set the button as vertically center in table view cell and hide the label.
if have tried following code for my implementation but button does not come in vertical center position.
if([labelString isEqualToString:#""]){
_label.hidden = YES;
_button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
}
it would be helpful if get suggestion for fixing this problem.
One approach: use dynamic setting of constraints.
In InterfaceBuilder:
Put your button and label inside an enclosing wrapper view.
Constrain the wrapper view to be centered in the main view.
Constrain the button and the label to be of fixed height, and the top of the label to be constrained to the bottom of the button (with spacing if desired).
Constrain the top of the wrapper to the top of the button.
Constrain the bottom of the wrapper with two separate constraints: one to the bottom of the button (but make this inactive), and one to the bottom of the label (active.).
Make both of those constraints outlets so that you can access them in code.
Leave the wrapper height otherwise unconstrained. Thus it will "shrink-wrap" to the combined height of the button and label.
In code:
When the label is empty, activate the constraint that pins the wrapper to the button instead of to the label:
BOOL hideLabel = [labelString isEqualToString:#""];
_label.hidden = hideLabel;
_wrapperToButtonConstraint.active = hideLabel;
_wrapperToLabelConstraint.active = !hideLabel;
Now the shrink-wrap effect will make the wrapper have the same height as the button, and since the wrapper is centered, so will the button be centered.
Have a look at the screenshot
I using stackview to stack the textfield, view, and tableview together. When I doing the constraint on the view,
I set the left constraint is 20, and right constraint as 20 on the both button, more over,
I add the width constraint on the both button but ended up the button show the width is different and cannot looks center on the simulator.
How do I make it to be center?
I'm working under the assumption you want your view to look like this:
V: textField - viewWithButtons - tableView
For your buttons, I'd highlight them and make them into a horizontal stack. Under attributes inspector, make the alignment fill, distribution fill equally, spacing 8 (or whatever you want).
From there, click your textFieldView, horizontalStackViewiewWithButtons, and tableView and then turn those into a vertical stack. From there, select your verticalStack from the document outline and click the Pin button at the lower-right corner of the screen. Left and right pins are 0, top pin is "Use Standard Value"
From there, work your way "inward" when you add constraints. The outer stack is mostly taken care of. You'll probably want to add a pin for the height of your textFieldView and your horizontalStackViewWithButtons.
I am also new to ios just my suggestion try this.
In storyboard set width constraint for both buttons
Create references for that constraints in your swift code.
#IBOutlet weak var height1: NSLayoutConstraint!
#IBOutlet weak var height2: NSLayoutConstraint!
In your viewwill appear method calculate width using your screen width for example
let screenSize: CGRect = UIScreen.mainScreen().bounds;
let width = screenSize.width;
//Then you need to remove the constraint spaces so
left(10) - Button1 - middle (10) - Button2 - Right(10)
So toatal 30
let width_available = screenSize.width - 30;
height2.constant = width_available/2
height1.constant = width_available/2
Edit 1:
The real simple solution just set equal width for both buttons from the storyboard.
Also you need horizontal spacing between the buttons
I have a button in a view which is in the footer of a tableview (UITableViewController). Why is the button stretching when I try to apply the following code to it?
And I apply the code:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Make footerview so it fill up size of the screen
// The button is aligned to bottom of the footerview
// using autolayout constraints
self.tableView.tableFooterView = nil
self.footerView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.tableView.frame.size.height - self.tableView.contentSize.height - self.footerView.frame.size.height)
self.tableView.tableFooterView = self.footerView
}
I was following the answer on this question:
Add button on top of UITableViewController (Swift)
Thanks!
If you read the question (whose link you posted) carefully, you can see that he resizes the view to take up the rest of the screen that is remaining after your table. Your button is the same size as your view, that is why it is stretching up. You need to add constraints which bind your button to the bottom of the view but not the top. Because if you bind the top and the bottom both to the view it will stretch.
Here is the example.
1. When you do not bind the button to the top. Notice that in the constraints, I do not have any constraint that specify the top of the button.
When you bind your button to the the top of the view. In this, I set a constraint which bind the button top to the view top. It stretches my button to take up the whole space as the view(which is similar to your case)
Hope this helps. :)