I'm new to the Swift and trying to add label programmatically to UIView which is embedded into scroll view.
Actually, i want to have something with header (picture and title for half of the screen) and content below (text with unknown length and images).
I'm added a view container and depending on content i want to add label or image view.
Most of the time it will be like that:
text
image
text
image
and so on.
Add label code (Swift 4):
let label = UILabel()
label.numberOfLines = 0
label.text = blogItem.text
containerView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: containerView, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: containerView, attribute: NSLayoutAttribute.leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: containerView, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: containerView, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0).isActive = true
The problem is that text is truncated, when comes to the end of the screen and no scrolling at all.
How can i add bottom constraint correctly (i believe that's all happens because of it?) to make expected behaviour?
Make sure you created a proper hierarchy for working UIScrollView:
Add a scrollView to the hierarchy and use autolayout to properly layout it, e.g., if it is supposed to cover the whole view of the viewController:
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
])
Then you need to add a contentView to the scrollView and provide a proper layout constraints for it, so if you want vertically scrollable scrollView in the example I started above, you need following autolayout constraints:
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// horizontal anchors of contentView are constrained to scrollView superview
// to prevent it from scrolling horizontally
contentView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
contentView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
// but vertical anchors of contentView are constrained to
// scrollView to allow scrolling
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
])
Notice here that I constrained the leftAnchor and rightAnchor of the contentView to the self.view rather than to scrollView to make it of fixed width. However, top and bottom anchors are constrained to the scrollView, so they are expanded and scrollable when contentView needs more space.
Now you add to the contentView all the content that you want, and you lay it out using autolayout as if the contentView was a view with infinite height - scrollView will take care of presenting it whole by scrolling. So in my example if the only content would be one huge UILabel with many lines (label is a subview of contentView):
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leftAnchor.constraint(equalTo: contentView.leftAnchor),
label.rightAnchor.constraint(equalTo: contentView.rightAnchor),
label.topAnchor.constraint(equalTo: contentView.topAnchor),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
Try to go over your code and check your constraints. It might be that you miss constraints in both cases, and the fact that it works in first case is just a coincidence.
Related
Description of the issue
I have a label to the left side of a plot view in an iOS App.
I have succeeded in rotatif the label 90 degrees, as pictured below, using the code copied in a following section :
However, as soon as I change the text to "Speed (Km/h):, the label becomes wider, as shown below :
Layout
There are no relevant contraints were set in interface builder. The only contraints set here are :
- vertical centering of the label
- plot view's right, top, and bottom edges stick to view's right, top, and bottom edges
The rest is set in code
Code
func setup(speedArray: [Float32]) {
//Convert speed to Km/h from m/s
let speedArrayKMH = speedArray.map({$0*3.6})
//Draw Chart
//This only styles the chart (colors, user interaction, etc...
//no code in this fuction that affects layout)
setupSpeedChart(speedArray: speedArrayKMH)
//
// Customise speed label
//
//Label text
chartTitleLabel.textAlignment = .center
//Text color
self.chartTitleLabel.textColor = BoxTextColor
//Rotate
chartTitleLabel.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi/2))
//Constrain
let C1 = NSLayoutConstraint(item: chartTitleLabel, attribute: .leadingMargin, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0)
let C2 = NSLayoutConstraint(item: chartTitleLabel, attribute: .trailingMargin, relatedBy: .equal, toItem: speedLineChart, attribute: .leading, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([C1, C2])
}
What I've tried
I've tried a number of construit and size combinations, including :
fixing the label's frame property (height and width)
adding a constraint for the label's width and height
nothing seems to work
Adding the following constraint for label width for example produces this :
let C3 = NSLayoutConstraint(item: chartTitleLabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 45)
there are few key thing missing
first you forgot to use translateAutoReaizingMaskIntoConstraints
go to storyboard select your label go to inspector under label there is autoshrink set it to minimum font size set the size to 11
then do the following in view didload
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myLabel.text = "speed in km"
myLabel.textAlignment = .center
myLabel.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi/2))
myLabel.translatesAutoresizingMaskIntoConstraints = false
let c1 = NSLayoutConstraint(item: myLabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 200)
let c2 = NSLayoutConstraint(item: myLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 60)
let c3 = NSLayoutConstraint(item: myLabel, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: -60)
let c4 = NSLayoutConstraint(item: myLabel, attribute: .centerY, relatedBy: .equal, toItem: myImageView, attribute: .centerY, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([c1, c2, c3, c4])
}
I have tried to update the text to new text with more character count than previous and I worked just fine
The issue lies in this part of the description of transform in Apple's docs: https://developer.apple.com/documentation/uikit/uiview/1622459-transform
In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame.
So, when you change the text of the label, your constraints are related to the untransformed frame.
The fix is to embed the label in a "container" UIView and constrain the container's width to the height of the label, and the height to the width of the label.
Set up your xib like this (I use contrasting background colors to make it easy to see frames):
Note that I've given the container view width and height constraints of 80. Edit each of those constraints and set them as "Placeholders":
We'll be setting new constraints in code, so these will be removed at build time, but will satisfy IB's layout checking.
In awakeFromNib() in your custom class, add the new width and height constraints and apply the rotation transform:
override func awakeFromNib() {
super.awakeFromNib()
// set HUGGING priority on title label to REQUIRED for both axis
chartTitleLabel.setContentHuggingPriority(.required, for: .horizontal)
chartTitleLabel.setContentHuggingPriority(.required, for: .vertical)
NSLayoutConstraint.activate([
// constrain title container view WIDTH equal to title label HEIGHT
// set the constant to add padding on left and right of rotated title label
// here it is set to 12, which gives 6-pts padding on each side
chartTitleContainerView.widthAnchor.constraint(equalTo: chartTitleLabel.heightAnchor, constant: 12.0),
// constrain title container view HEIGHT equal to title label WIDTH
chartTitleContainerView.heightAnchor.constraint(equalTo: chartTitleLabel.widthAnchor, constant: 0.0),
])
// rotate the title label
chartTitleLabel.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi/2))
// un-comment after development
// chartTitleLabel.backgroundColor = .clear
// chartTitleContainerView.backgroundColor = .clear
}
Now, in your setup() func (where, I'm assuming, you add the "plot view" as a subview), add the constraints for the plot view:
func setup(speedArray: [Float32]) {
//Convert speed to Km/h from m/s
let speedArrayKMH = speedArray.map({$0*3.6})
// assuming setupSpeedChart() creates speedLineChart view and adds it to self
//Draw Chart
//This only styles the chart (colors, user interaction, etc...
//no code in this fuction that affects layout)
setupSpeedChart(speedArray: speedArrayKMH)
NSLayoutConstraint.activate([
// constrain chart view LEADING to title container view TRAILING
speedLineChart.leadingAnchor.constraint(equalTo: chartTitleContainerView.trailingAnchor, constant: 0.0),
// constrain chart view top, bottom and trailing to self
speedLineChart.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
speedLineChart.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
speedLineChart.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
])
}
This is the result:
and with the "dev" colors turned off:
You can now change the text of the title label and it will remain centered vertically and horizontally, without changing the horizontal size / spacing.
I have a contentview inside a scrollview along with some other views, and scrollview height will change based on contentview height, and the height of the contentview is decided by the height of its subviews.
I am adding two subviews to contentview programmatically.
childView1 = CustomView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 200))
childView2 = CustomView(frame: CGRect(x: 0, y: 210, width: self.view.frame.width, height: 200))
contentView.addSubview(childView1)
contentView.addSubview(childView2)
// childView1 top should be equal to contentView top
let topConstraint = NSLayoutConstraint(item: childView1, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 0)
// childView2 bottom should be equal to contentView bottom
let bottomConstraint = NSLayoutConstraint(item: childView2, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 0)
// childView1 leading should be equal to contentView leading
let leadingConstraint1 = NSLayoutConstraint(item: childView1, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 0)
// childView1 trailing should be equal to contentView trailing
let trailingConstraint1 = NSLayoutConstraint(item: contentView1, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 0)
// childView2 leading should be equal to contentView leading
let leadingConstraint2 = NSLayoutConstraint(item: childView2, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 0)
// childView2 trailing should be equal to contentView trailing
let trailingConstraint2 = NSLayoutConstraint(item: childView2, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 0)
// Vertical Space between two child views is 10
let constraintTwoSubViews = NSLayoutConstraint(item: childView1, attribute: .bottom, relatedBy: .equal, toItem: childView2, attribute: .top, multiplier: 1, constant: 10)
contentView.addConstraints([topConstraint , bottomConstraint , leadingConstraint1,trailingConstraint1 , leadingConstraint2 , trailingConstraint2 , constraintTwoSubViews])
The problem is that the contentView height is not growing as per the subviews.
Because of that my scroll views is not growing.
When I am adding second child view, I do not want to specify y = 210
, I want its position to be calculated by space constraint between
two child views, Is it possible?
How can I make content view height grow as per its subviews?
What I do when I need a scrollable view:
I add a scrollView to the hierarchy and use autolayout to properly layout it, e.g., if it is supposed to cover the whole view of the viewController:
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
])
Then you need to add a contentView to the scrollView and provide a proper layout constraints for it, so if you want vertically scrollable scrollView in the example I started above, you need following autolayout constraints:
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// horizontal anchors of contentView are constrained to scrollView superview
// to prevent it from scrolling horizontally
contentView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
contentView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
// but vertical anchors of contentView are constrained to
// scrollView to allow scrolling
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
])
Notice here that I constrained the leftAnchor and rightAnchor of the contentView to the self.view rather than to scrollView to make it of fixed width. However, top and bottom anchors are constrained to the scrollView, so they are expanded and scrollable when contentView needs more space.
Now you add to the contentView all the content that you want, and you lay it out using autolayout as if the contentView was a view with infinite height - scrollView will take care of presenting it whole by scrolling. In your case I think you can use UIStackView to lay out those two views. However, CustomView's frames will be calculated using autolayout, so I don't think you can rely on setting the frames directly - therefore I use constraints to set their heights too (widths will be stretched by the stackView):
let stack = UIStackView()
stack.spacing = 10
stack.distribution = .fill
stack.alignment = .fill
stack.axis = .vertical
contentView.addSubview(stack)
stack.addArrangedSubview(childView1)
stack.addArrangedSubview(childView2)
childView1.translatesAutoresizingMasks = false
childView2.translatesAutoresizingMasks = false
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: stack.topAnchor),
contentView.leadingAnchor.constraint(equalTo: stack.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: stack.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: stack.bottomAnchor),
// setting their heights using constraints
childView1.heightAnchor.constraint(equalToConstant: 200),
childView2.heightAnchor.constraint(equalToConstant: 200),
])
This question already has answers here:
How to add constraints programmatically using Swift
(19 answers)
Closed 6 years ago.
I'm new to iOS development. I want to build layout without storyboard or xib/nib. So I am trying to add constraints programmatically.
I have searched some tutorials about add constraints programmatically. But the view can't show correctly.
I'm trying this code in my ViewController class:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let testView = UIView()
self.view.addSubview(testView)
// Add Constraints
self.view.translatesAutoresizingMaskIntoConstraints = false
testView.translatesAutoresizingMaskIntoConstraints = false
let top = NSLayoutConstraint(item: testView, attribute: .top, relatedBy: .equal
, toItem: self.view, attribute: .top, multiplier: 1, constant: 50.0)
let bottom = NSLayoutConstraint(item: testView, attribute: .bottom, relatedBy: .equal
, toItem: self.view, attribute: .bottom, multiplier: 1, constant: -50.0)
let leading = NSLayoutConstraint(item: testView, attribute: .leading, relatedBy: .greaterThanOrEqual, toItem: self.view, attribute: .leading, multiplier: 1, constant: 50.0)
let trailing = NSLayoutConstraint(item: testView, attribute: .trailing, relatedBy: .greaterThanOrEqual, toItem: self.view, attribute: .trailing, multiplier: 1, constant: -50.0)
self.view.addConstraints([top, bottom, leading, trailing])
}
Generally, they don't need to define the size of view or constraints about width and height in tutorials. But the views can be shown on their tutorial. In my case, my testView can't show it in the app even top, bottom, leading and trailing constraints have been set. Am I missed something? What's the problem?
one of the tutorials:
http://www.knowstack.com/swift-nslayoutconstraint-programatically-sample-code/
Edit:
Let's me explain more. I want to create a UIView that suitable for universal device. The UIView has top, bottom, leading and trailing constraints with constant: 10. So, I don't need to set size of UIView.
Expected Result (I am using draw tool to simulate the result)
This is an example of a view constraint to the bottom of the screen with height equal to 80:
var yourView = UIView()
yourView.translateAutoresizingMaskIntoConstraints = false
yourSuperView.addSubview(yourView)
// Pin the leading edge of yourView to the leading edge of the main view
yourView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
// Pin the trailing edge of yourView to the leading trailing edge
yourView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true
// Pin the bottomedge of yourView to the margin's leading edge
yourView .bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true
// The height of your view
yourView.heightAnchor.constraintEqualToConstant(80).active = true
You have two issues:
Do not set self.view.translatesAutoresizingMaskIntoConstraints = false. You only need to set translatesAutoresizingMaskIntoConstraints for the subview you are adding.
You are using .greaterThanOrEqual constraints instead of .equal constraints. The problem with that is that leaves a lot of wiggle room and you are getting values that cause your testView to have 0 width. If you change to .equal that will properly constrain the values.
I want to create such a UI that have two buttons stick to the bottom of the screen and a UIScrollView above them. I am using Chatto framework and would be great if anyone could give me an example how to do that based on https://github.com/badoo/Chatto/tree/master/ChattoApp/ChattoApp.
Here is the visualization of view that I'd like to have.
there is a better solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView for floating button:
Swift 4 example
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}
You can easily do this using constraints. If you're using storyboard, you can set the constraints up using their "Pin" and "Align" features. If you're building it in code, you'll want to programmatically set up your constraints. Just be sure to add all the necessary constraints to fully define how the view should appear.
pseudo example with just one button:
let button = UIButton()
self.view.addsubview(button)
// pin button to bottom of superview,
let buttonBottomConstraint = NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
self.addConstraint(buttonBottomConstraint)
// left of superview,
// right of superview,
// and height
let scrollView = UIScrollView()
self.view.addsubview(scrollView)
// and bottom edge to top edge of button
let scrollViewBottomConstraint = NSLayoutConstraint(item: scrollView, attribute: .Bottom, relatedBy: .Equal, toItem: button, attribute: .Top, multiplier: 1.0, constant: 0.0)
self.addConstraint(scrollViewBottomConstraint)
// left of superview,
// right of superview,
// pin scrollview to top of superview,
Add a full screen scrollview then add a UIView across the bottom (anchoring to bottom of screen). Then add the 2 buttons to the UIView.
If you want to make it semi transparent then make the UIView background color to be clearColor and then add a UIView with alpha of say 0.6 and add this to your original UIView above the buttons.
I am attempting to display a UILabel centered at the bottom of its parent view, with leading and trailing set to stretch to the parent view's bounds. Unfortunately, the label isn't appearing on screen at all. I have verified the parent view is correctly filling the entire screen as desired.
//set up parent view
let vibrancyEffect = UIVibrancyEffect(forBlurEffect: blurEffect)
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
vibrancyEffectView.frame = blurEffectView.bounds
vibrancyEffectView.autoresizingMask = .FlexibleWidth | .FlexibleHeight
//Label for vibrant text
let vibrantLabel = UILabel()
vibrantLabel.text = "My Label"
vibrantLabel.textColor = UIColor(white: 0.64, alpha: 1)
vibrantLabel.textAlignment = .Center
vibrantLabel.sizeToFit()
vibrantLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
vibrancyEffectView.contentView.addSubview(vibrantLabel)
vibrancyEffectView.addConstraint(NSLayoutConstraint(item: vibrantLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: vibrancyEffectView, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 15))
vibrancyEffectView.addConstraint(NSLayoutConstraint(item: vibrantLabel, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: vibrancyEffectView, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0))
vibrancyEffectView.addConstraint(NSLayoutConstraint(item: vibrantLabel, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: vibrancyEffectView, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0))
vibrancyEffectView.addConstraint(NSLayoutConstraint(item: vibrantLabel, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: vibrantLabel, attribute: NSLayoutAttribute.Height, multiplier: 1, constant: 30))
blurEffectView.contentView.addSubview(vibrancyEffectView)
Could this be due to the autoresizing mask set on the parent, or is my auto layout constraints incorrect? Also I was wondering what is the best way to handle the height - I want to ensure the text fits it in.
I think a couple of your constraints are wrong.
You're pinning the bottom of the label to the bottom of the vibrancy view, with a constant of 15 - this will pin it 15 points below the bottom.
You're also pinning the height of the label to its own height plus 30 - this is unsatisfiable and I'm surprised you're not seeing error logs. A label doesn't need height constraints as it will have a intrinsic content size based on the text value - you also don't need the sizeToFit call. To make the label flow onto multiple lines, set the numberOfLines property to zero.
Type of constrains to be added for label.
left, top, width and height
TOP constrain(sample):
mylabel.setTranslatesAutoresizingMaskIntoConstraints(false)
self.addConstraint(NSLayoutConstraint(
item:mylabel, attribute:NSLayoutAttribute.Top,
relatedBy:NSLayoutRelation.Equal, toItem:myView,
attribute:NSLayoutAttribute.Bottom, multiplier:1.0, constant: 0.0))