UIView in UIScrollView respects some constraints but not other - ios

I need to add a containerView inside a UIScrollView, and then add multiple subviews in the containerView. For some reason, the containerView does not respect the top/bottom/left/rightAnchor constraints, but it works with width/height/centerX/centerYAnchor
NOTE: If the superview is a UIView instead of a UIScrollView, it works fine.
The project is 100% code based. Using Swift 4.1 and Xcode 9.4
This does not work
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true
containerView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
containerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
This works
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
containerView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
containerView.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor).isActive = true
In both cases the scrollView.constraints array includes 4 constraints total.
The interesting thing is that the printout of them is different. Some of the constraints that don't work (.top and .left) are printed using the Autolayout Visual Format Language. Also, note the (LTR) in the third one:
ScrollView [
<NSLayoutConstraint:V:|-(0)-[UIView] (active, names: '|':UIScrollView:)>,
<NSLayoutConstraint:UIView.bottom == UIScrollView.bottom (active)>,
<NSLayoutConstraint:H:|-(0)-[UIView](LTR) (active, names: '|':UIScrollView:)>,
<NSLayoutConstraint:UIView.right == UIScrollView.right (active)>]
The constraints that work are printed as follows:
ScrollView [
<NSLayoutConstraint:UIView.width == UIScrollView.width (active)>,
<NSLayoutConstraint:UIView.height == UIScrollView.height (active)>,
<NSLayoutConstraint:UIView.centerX == UIScrollView.centerX (active)>,
<NSLayoutConstraint:UIView.centerY == UIScrollView.centerY (active)>]
I researched StackOverflow and found a couple of questions like this, but they didn't really help me explain what the problem is (or the UIScrollView requirements for constraints).
Any ideas?

UIScrollView needs some contents in it to be scrolled. The view you are adding (inside scrollview), does not have size (height and width), so scroll view can't identify size of its content.
Add size for a view (inside scrollview) and it will work.
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true
containerView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
containerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
// Size constraints
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
// To check scrolling of container view; try this
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor + 50.0).isActive = true
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor + 50.0).isActive = true

It's because the UIScrollView requires it's contentSize to be set in some way. By anchoring the UIView's layout to the sides of the UIScrollView, auto layout still doesn't have an explicit idea of what contentSize of the UIScrollView.
Since the UIScrollView is probably anchored to some parent view, the height and width of the UIScrollView is already defined. By giving a UIView these constraints, auto layout can determine the size of the UIView and then use that size to set the contentSize of the UIScrollView.

Go through the following points in order to use scrollview in your application.
1. First add UIScrollview and give it constrain in view(left, right,width,height).[![enter image description here][1]][1]
2. Now each scrollview has content view which should be there , we cannot add our required views directly to UIScrollview.
3. Add view to scrollview(we name it content view) , give it top,bottom, left and right constrain. Apart from these we need to add height and width constrain to the content view.
4. If you want to have vertical scrollview then give width equal to scrollview and a proper height (like height constrain = 600)or greater than scrollview height.
5. If you want to have horizontal scrollview then give height equal to scrollview and width greater than actual width of scrollview.
Have a look at the constrain of content view added below

Related

Possible to have fine-grained horizontal Auto Layout control in vertical UIStackView?

I'm currently working with a vertical UIStackView in which each view has a UILabel to be displayed on the left and a UIButton to be displayed on the right. The leading constraint for the label should be different than the trailing constraint for the button and the entire view should take up the width of the screen. The alignment for the stackview is fill and the distribution is fillEqually.
The problem I'm running into is that Auto Layout doesn't seem to be respecting the trailing constraint that I'm trying to set for the button on fill alignment (or any other alignment, for that matter). No matter what I put for it, the system sets it to 20. I've tried center, leading, and trailing alignments but they don't work for what I'm trying to do. Is it possible to have the degree of horizontal layout control I'd like with a vertical stackview?
My constraints (radioButton trailing constraint constant is set to 0):
private func sharedInitialization() {
translatesAutoresizingMaskIntoConstraints = false
textLabel.translatesAutoresizingMaskIntoConstraints = false
radioButton.translatesAutoresizingMaskIntoConstraints = false
addSubview(textLabel)
addSubview(radioButton)
NSLayoutConstraint.activate([
radioButton.widthAnchor.constraint(equalToConstant: 30),
radioButton.heightAnchor.constraint(equalToConstant: 30),
radioButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
radioButton.centerYAnchor.constraint(equalTo: centerYAnchor),
textLabel.trailingAnchor.constraint(equalTo: radioButton.leadingAnchor, constant: -10),
textLabel.topAnchor.constraint(equalTo: topAnchor, constant: 22),
textLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
bottomAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 22)])
}
Visual debugger:
I've looked at Prevent vertical UIStackView from stretching a subview? but that doesn't seem quite applicable to my situation. Thanks in advance for any help!

setting textfield constraints to hold effective in screen sizes 4.5 in to 6.5 in

if I set fixed width it either appears too large for small screen (4.5 in) or too small for large screen (6.5 in)
and
is there any special way to ensure the constraints hold good in all
constraints
Like Jatin mentioned in the comments, you can use leading and trailing anchors relative to the view like this,
textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
textField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
Or, you could set the width as a multiplier to the width of the view.
textField.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.80).isActive = true
Note: Change the constant and multiplier values to suit your needs.

How can I get the height of a view if I set its anchors

I am currently trying to access the height of a view which I previously set anchors for. So for example, I set the left, right, top, and bottom anchors of searchTable using the following in my ViewController:
searchTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
searchTable.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
searchTable.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
searchTable.bottomAnchor.constraint(equalTo: menuBar.topAnchor).isActive = true
searchTable is an object of a class that I created that inherits from UIView and has a UIImageView in it. I constrain the UIImageView by using the following in the init function:
imageView.topAnchor.constraint(equalTo: topContainer.topAnchor, constant: 10).isActive = true
imageView.leftAnchor.constraint(equalTo: topContainer.leftAnchor, constant: 10).isActive = true
imageView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: topContainerMultiplier * imageProfileMultiplier).isActive = true
imageView.widthAnchor.constraint(equalTo: self.heightAnchor, multiplier: topContainerMultiplier * imageProfileMultiplier).isActive = true
where:
let topContainerMultipler: Double = 1 / 7
let imageProfileMultipler: Double = 1 / 5
Inside the init function of searchTable, I try want to be able to set the corner radius to be half the image size. I tried to use self.frame.height, self.frame.size.height, self.bounds.height and also getting the .constant value of the self.heightAnchor constraint, but all returns 0.
I would think that there is a solution to get around this, but I haven't been able to find it.
You are querying the height of your view's frame after defining your constraints, but before layout has actually resolved those constraints into a frame rectangle. One approach would be to call layoutIfNeeded to force layout to happen immediately, then use view.frame.size.height. The init method is probably not an appropriate place to do this, as opposed to perhaps inside viewDidLoad on the controller. Or you might do this in viewDidLayoutSubviews to recalculate the corner radius every time your view's layout is updated (in which case you won't need layoutIfNeeded).
Don't use a height constraint if you already use top, bottom, leading, and trailing.
Try to visually debug your view by adding a background color, or using the debug view hierarchy button.
To get the height of a view you should use view.frame.size.height
EDIT (OP question edited)
Your problem is that you try to access the object height when it's just initialized but not displayed yet. You should create a function that update your UI and call it after your view is loaded.

UIScrollView Subviews not expanding to fill width (Autolayout)

I'm using the following code to constrain a view to the left and right anchors of a parent UIScrollView.
Despite the right anchor and the left anchor being set to the ScrollView's left and right anchors, the view does not expand to fill the scrollview.
Note: The gray background in this image is the UIScrollView's background, so I know that's properly constrained to its parent view.
Code:
self.wtfView.translatesAutoresizingMaskIntoConstraints = false
self.wtfView.backgroundColor = UIColor.orange
self.wtfView.topAnchor.constraint(equalTo: self.passwordField.bottomAnchor, constant: 40.0).isActive = true
self.wtfView.leftAnchor.constraint(equalTo: self.containerView.leftAnchor, constant: 40.0).isActive = true
self.wtfView.rightAnchor.constraint(equalTo: self.containerView.rightAnchor, constant: 40.0).isActive = true
self.wtfView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
self.wtfView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, constant: 40.0).isActive = true
https://imgur.com/a/U88iW
Edit:
The following code works correctly, but I would prefer to use the left+right anchor technique to specify the width, and not at a width constraint. Shouldn't that be possible?
self.wtfView.translatesAutoresizingMaskIntoConstraints = false
self.wtfView.backgroundColor = UIColor.orange
self.wtfView.topAnchor.constraint(equalTo: self.passwordField.bottomAnchor, constant: 40.0).isActive = true
self.wtfView.leftAnchor.constraint(equalTo: self.containerView.leftAnchor, constant: 40.0).isActive = true
self.wtfView.widthAnchor.constraint(equalTo: self.containerView.widthAnchor, constant: -80.0).isActive = true //THE DIFFERENT ONE
self.wtfView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
self.wtfView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, constant: 040.0).isActive = true
The reason for this is that the contentView of the UIScrollView still doesn't know that you want it to take up the width of it's parentView.
You can fix this by adding the following constraint in iOS11:
self.containerView.contentLayoutGuide.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
This says "Hey, I want you to lock the content Width to the width of the superview.
Pre iOS 11 you can simply constrain a subview to both the parent view's left and right anchors AND the content view's left and right anchors.
Like so:
self.wtfView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 40.0).isActive = true
self.wtfView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 40.0).isActive = true
Much like, Aleksei's recommendation you are now constraining the width to a rigid value ( the width of the parent view ), and the scrollview will use that to decide the width of the scrollview.
may be try to provide:
self.wtfView.widthAnchor.constraint(equalTo: self.containerView.widthAnchor, constant: -40.0).isActive = true

How to make a constraint use the view with highest height from an array of views

Let's assume I have 4 views, 3 in a row and 1 in another row. The ones at the first row have dynamic height and we have no idea how tall they are.
I want to achieve the result from this image:
Basically I want to set View 4's top to bottom of highest view in first row. How can I achieve this?
Use greaterThanOrEqualTo:
view4.topAnchor.constraint(greaterThanOrEqualTo: view1.bottomAnchor, constant: 10).isActive = true
view4.topAnchor.constraint(greaterThanOrEqualTo: view2.bottomAnchor, constant: 10).isActive = true
view4.topAnchor.constraint(greaterThanOrEqualTo: view3.bottomAnchor, constant: 10).isActive = true
Put View1, View2 and View3 inside UIStackView (or regular UIView), and then add constraint between bottom of this new view and top of View4

Resources