Animating Hiding Subviews in UIStackView goes out of screen - ios

I'm trying to animate the hiding of the arranged subviews of a UIStackView:
UIView.animate(withDuration: 3, delay: 0) {
self.stackView.subviews.forEach({ $0.isHidden = true })
self.stackView.updateConstraintsIfNeeded()
self.stackView.layoutIfNeeded()
self.view.layoutIfNeeded()
} completion: { (_) in
self.children.forEach({ $0.removeFromParent() })
completion()
}
The problem is the views while animating expand in their width across the screen (out of screen bounds).
This is how the views are setup:
scrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
stackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
contentView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
contentView.addSubview(stackView)
scrollView.addSubview(contentView)
view.addSubview(scrollView)
scrollView.showsVerticalScrollIndicator = false
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20).isActive = true
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -50).isActive = true
Question: How can I animate it in such a way, that it does not change width during the animation?

I don't know if this is a weird issue of the UIStackView or it serves some purpose, but: putting an "invisible" UIView into your UIStackView will fix the issue. Tag it, so when hiding the other elements it remains visible:
let imposterView = UIView()
imposterView.tag = 999
stackView.addArrangedSubview(imposterView)
Of course handle your hiding logic accordingly:
stackView.subviews.forEach({
if $0.tag != 999 {
$0.isHidden = true
}
})

Related

try to set up a Dynamic UIScrollView programmatically but met problem

recently I tried to set up a dynamic UIScrollView according to this website : https://medium.com/#javedmultani16/uiscrollview-dynamic-content-size-through-storyboard-in-ios-fb873e9278e
I tried to make one step by step, but seems like I have misunderstood something but I can't figure it out. Here is my code, I tried to add 30 UITextField and set the UIScrollView equal height with those contents.
Problem I met, the scrollview not work correctly, it can only scroll a little bit, like I can only scroll to about 8 or 9 line , the others below I can't scroll down.
override func viewDidLoad() {
super.viewDidLoad()
//Step 1
let scrollview = UIScrollView()
view.addSubview(scrollview)
scrollview.translatesAutoresizingMaskIntoConstraints = false
scrollview.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollview.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollview.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollview.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollview.alwaysBounceVertical = true
//Step 2
let oneview = UIView()
scrollview.addSubview(oneview)
oneview.translatesAutoresizingMaskIntoConstraints = false
oneview.topAnchor.constraint(equalTo: scrollview.topAnchor).isActive = true
oneview.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor).isActive = true
oneview.leadingAnchor.constraint(equalTo: scrollview.leadingAnchor).isActive = true
oneview.trailingAnchor.constraint(equalTo: scrollview.trailingAnchor).isActive = true
oneview.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
let heightConstraint = oneview.heightAnchor.constraint(equalTo: view.heightAnchor)
heightConstraint.isActive = true
heightConstraint.priority = .defaultLow
//Step 3
for i in 1...30{
let field = UITextField()
field.placeholder = "This is line "+String(i+1)
field.backgroundColor = .gray
field.isUserInteractionEnabled = false
oneview.addSubview(field)
field.translatesAutoresizingMaskIntoConstraints = false
field.widthAnchor.constraint(equalTo: oneview.widthAnchor, multiplier: 0.8).isActive = true
field.heightAnchor.constraint(equalToConstant: 50).isActive = true
field.centerXAnchor.constraint(equalTo: oneview.centerXAnchor).isActive = true
field.topAnchor.constraint(equalTo: oneview.topAnchor, constant: CGFloat(100*i)).isActive = true
}
}
The whole point of using this dummy subview (oneview in your code) is so that its contents can tell the dummy subview what height it should be, and the scroll view's content size will fit that height.
However, your textfield's constraints do not suggest a height for oneview!. All your textfields' constraints say is the text fields
should be some distance below the top of oneview.
should be a certain height
should have the same centre X as oneview
should have the same width as oneview
To satisfy the above, oneview's height doesn't need to change at all. The layout engine can just place your text fields outside of the bounds of oneview, and still satisfy the above constraints. (Think about it!)
But, if you add one more constraint to the last text field, that it
should be a certain distance above the bottom of oneview
then oneview has to resize in order to satisfy that. You are indirectly implying a height for oneview, because you are saying
for the last text field, I want this much space to be above it, and this much space to be below it.
We could just make that "certain distance" zero as well. Here's how you would do it in code:
// in the loop...
if i == 30 {
field.bottomAnchor.constraint(equalTo: oneview.bottomAnchor).isActive = true
}
An even better way to tell oneview its height would be to use a UIStackView. Replace steps 2 and 3 with:
let oneview = UIStackView()
oneview.alignment = .fill
oneview.distribution = .equalSpacing
oneview.spacing = 50
oneview.axis = .vertical
scrollview.addSubview(oneview)
oneview.translatesAutoresizingMaskIntoConstraints = false
oneview.topAnchor.constraint(equalTo: scrollview.topAnchor).isActive = true
oneview.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor).isActive = true
oneview.leadingAnchor.constraint(equalTo: scrollview.leadingAnchor).isActive = true
oneview.trailingAnchor.constraint(equalTo: scrollview.trailingAnchor).isActive = true
oneview.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
let heightConstraint = oneview.heightAnchor.constraint(equalTo: view.heightAnchor, constant: 25)
heightConstraint.isActive = true
heightConstraint.priority = .defaultLow
for i in 1...30{
let field = UITextField()
field.placeholder = "This is line "+String(i+1)
field.backgroundColor = .gray
field.isUserInteractionEnabled = false
oneview.addArrangedSubview(field)
field.translatesAutoresizingMaskIntoConstraints = false
field.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
The problem is the first top view should be anchored to the top of the content view and the last bottom view should anchored to the bottom of the contentView
Extensions
extension UIScrollView {
func assignKeyboardObservers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillDissmiss),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
#objc fileprivate func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
insetScrollView(insetBottom: keyboardHeight)
}
}
#objc fileprivate func keyboardWillDissmiss(_ notification: Notification) {
insetScrollView(insetBottom: 0)
}
fileprivate func insetScrollView(insetBottom: CGFloat) {
var inset:UIEdgeInsets = contentInset
inset.bottom = insetBottom
UIView.animate(withDuration: 0.2, animations: {
self.contentInset = inset
})
}
}
extension UIViewController {
func addScrollViewToView(paddingContentView:UIEdgeInsets = .zero, anchorScroll:( _ scrollView: inout UIScrollView)->())->UIView {
let tapToDismiss = UITapGestureRecognizer(target: self, action: #selector(dimissKeyboard(_:)))
tapToDismiss.cancelsTouchesInView = false
view.addGestureRecognizer(tapToDismiss)
var scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.bounces = true
scrollView.isScrollEnabled = true
scrollView.assignKeyboardObservers()
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
anchorScroll(&scrollView)
scrollView.addSubview(contentView)
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: paddingContentView.top),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: paddingContentView.left),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -paddingContentView.right),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -paddingContentView.bottom),
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
return contentView
}
#objc fileprivate func dimissKeyboard(_ sender: UITapGestureRecognizer) {
view.endEditing(true)
}
}
How to use it
import UIKit
class TESTViewController: UIViewController {
private var contentView:UIView!
override func viewDidLoad() {
super.viewDidLoad()
contentView = addScrollViewToView(anchorScroll: { (scrollView) in
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
})
let stackView:UIStackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 3
stackView.alignment = .fill
stackView.distribution = .fillEqually
for i in 1...30{
let field = UITextField()
field.translatesAutoresizingMaskIntoConstraints = false
field.placeholder = "This is line "+String(i+1)
field.backgroundColor = .gray
field.isUserInteractionEnabled = false
field.heightAnchor.constraint(equalToConstant: 50).isActive = true
stackView.addSubview(field)
}
contentView.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
}

How to set constraint relationship to view, not subviews programmatically?

I am trying to setup a couple of views programmatically. On my main view I add two subviews, one anchored to the top and one to the bottom:
//Button View
view.addSubview(buttonsLabel)
buttonsLabel.translatesAutoresizingMaskIntoConstraints = false
buttonsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
buttonsLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
buttonsLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
buttonsLabel.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5, constant: -20).isActive = true
//Calculator View
calcLabel.layer.cornerRadius = 25
view.addSubview(calcLabel)
calcLabel.translatesAutoresizingMaskIntoConstraints = false
calcLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
calcLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
calcLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
//calcLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
calcLabel.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5, constant: -40).isActive = true
This works fine, both views are 50% of the frame height (minus the constants) and both are shown (one at the top, one at the bottom). But when I try to add a third view, which is 75% of the frames height and which should be placed on top of the other two views, the layout is destroyed and everything is moved almost outside of the frame.
I am trying to anchor the third view to the bottom again:
thirdView.layer.cornerRadius = 25
view.addSubview(thirdView)
thirdView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
thirdView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
thirdView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
thirdView.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.75).isActive = true
This is how everything should look like (left the two views, right the third view on top:
Am I doing the anchors and constraints right (or whats abetter way) and how to add the constraint for the third view, so that it is 75% of the frames height and placed like in the image on top of everything.
Your code looks good the problem is else where, check the view hierarchy in the debugger to see which constraint(s) failed, perhaps you forgot translatesAutoresizingMaskIntoConstraints as beyowulf commented. you should be using constants as well, this makes code much more maintainable.
here is my implementation:
import UIKit
class ViewController: UIViewController {
//MARK: - SubViews
let topHalfView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.gray
return view
}()
let bottomHalfView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.gray
return view
}()
let threeQuarterView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.black
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//add, layout subviews with 9+ constraints
setupViews()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setupViews() {
self.view.addSubview(topHalfView)
self.view.addSubview(bottomHalfView)
self.view.addSubview(threeQuarterView)
let guide = self.view.safeAreaLayoutGuide
let spacing:CGFloat = 12
let viewHeight = self.view.frame.height - spacing
topHalfView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
topHalfView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: spacing).isActive = true
topHalfView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -spacing).isActive = true
topHalfView.heightAnchor.constraint(equalToConstant: viewHeight * 0.5).isActive = true
bottomHalfView.topAnchor.constraint(equalTo: topHalfView.bottomAnchor, constant: spacing).isActive = true
bottomHalfView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: spacing).isActive = true
bottomHalfView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -spacing).isActive = true
bottomHalfView.heightAnchor.constraint(equalToConstant: viewHeight * 0.5).isActive = true
threeQuarterView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
threeQuarterView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: spacing).isActive = true
threeQuarterView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -spacing).isActive = true
threeQuarterView.heightAnchor.constraint(equalToConstant: self.view.frame.height * 0.75).isActive = true
}
}
The View hierarchy:

UIScrollView not able to scroll?

I have this UIScrollView set up with a UIImageView and a UILabel.
However, it's not scrolling like I want it to. It's like it's not even there... How can I fix this? I just want it to be scrollable (even though theres nothing at the bottom to scroll to yet)
Thx in advance!
Here's my code:
// scrollView
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
// scrollView constraints
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
// Initialize profileImageView
profileImageView.image = #imageLiteral(resourceName: "neil")
profileImageView.translatesAutoresizingMaskIntoConstraints = false
profileImageView.layer.cornerRadius = 125 / 2
profileImageView.clipsToBounds = true
scrollView.addSubview(profileImageView)
// Add profileImageView constraints
profileImageView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 45).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 125).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 125).isActive = true
profileImageView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
// Add separator view
seperator.translatesAutoresizingMaskIntoConstraints = false
seperator.backgroundColor = UIColor.darkGray
scrollView.addSubview(seperator)
// seperator constraints
seperator.heightAnchor.constraint(equalToConstant: 2).isActive = true
seperator.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.8).isActive = true
seperator.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
seperator.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 20).isActive = true
//nameLabel
let nameLabelFont = UIFont.monospacedDigitSystemFont(ofSize: 36, weight: UIFont.Weight.heavy)
nameLabel.font = nameLabelFont
nameLabel.text = currentUser.name
nameLabel.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(nameLabel)
// nameLabel constraints
nameLabel.topAnchor.constraint(equalTo: seperator.bottomAnchor, constant: 10).isActive = true
nameLabel.leadingAnchor.constraint(equalTo: seperator.leadingAnchor).isActive = true
You can't scroll because scrollView.contentSize isn't bigger then your view.
If you want make your screen scrollable you should set scrollView.contentSize.
Put scrollView.contentSize = CGSize(width: 1000, height: 1000) before view.addSubview(scrollView) to set your scrollable area to 1000x1000.
Generally scrollView.contentSize should be calculated base on UI elements inside UIScrollView e.g. size of 10 images with space between them.

Programmatically layed out UIScrollView, and added auto layout to it's subviews, but it does not scroll

I am trying to figure out how UIScrollView works and I added some subviews to it with different backgroundColor properties. I layed out the subviews with ios9 autolayout but even if the views are outside of the screen, the UIScrollView still does not scroll.
import UIKit
class ViewController: UIViewController {
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.backgroundColor = .gray
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
let view1 = UIView()
view1.backgroundColor = .red
let view2 = UIView()
view2.backgroundColor = .blue
let view3 = UIView()
view3.backgroundColor = .green
let view4 = UIView()
view4.backgroundColor = .purple
let views = [view1, view2, view3, view4]
for view in views {
scrollView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
}
view1.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
view1.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
view1.heightAnchor.constraint(equalToConstant: 140).isActive = true
view1.widthAnchor.constraint(equalToConstant: 140).isActive = true
view2.topAnchor.constraint(equalTo: view1.bottomAnchor, constant: 100).isActive = true
view2.leftAnchor.constraint(equalTo: view1.rightAnchor).isActive = true
view2.heightAnchor.constraint(equalToConstant: 140).isActive = true
view2.widthAnchor.constraint(equalToConstant: 140).isActive = true
view3.topAnchor.constraint(equalTo: view2.bottomAnchor, constant: 50).isActive = true
view3.leftAnchor.constraint(equalTo: view1.rightAnchor).isActive = true
view3.heightAnchor.constraint(equalToConstant: 140).isActive = true
view3.widthAnchor.constraint(equalToConstant: 140).isActive = true
view4.topAnchor.constraint(equalTo: view3.bottomAnchor, constant: 20).isActive = true
view4.leftAnchor.constraint(equalTo: view1.rightAnchor).isActive = true
view4.heightAnchor.constraint(equalToConstant: 140).isActive = true
view4.widthAnchor.constraint(equalToConstant: 140).isActive = true
}
}
When using Autolayout in UIScrollViews you have to pin subviews both to the top and bottom of the scrollview which allows the scrollview to calculate its contentSize.
Adding this line fixes it:
view4.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true

Swift: I'm not able to make autolayout constraints work for my UIScrollView

I'm trying to make a simple layout that should scroll vertically when views are too many to fit on the screen.
So this is what I have so far:
I created a scrollview and a container view like that
let mainScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.isUserInteractionEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let containerView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
Then I added the scrollview to the main view, added the container view to the scrollview and a couple of labels to the container view
view.addSubview(mainScrollView)
mainScrollView.addSubview(containerView)
containerView.addSubview(firstLabel)
containerView.addSubview(secondLabel)
I pinned the scrollview to the main view, and the container view to the scrollview. After that I started adding the labels inside the container view like this
mainScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mainScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
mainScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mainScrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: mainScrollView.layoutMarginsGuide.topAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: mainScrollView.layoutMarginsGuide.bottomAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
firstLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
firstLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 30).isActive = true
firstLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
firstLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
secondLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: 750).isActive = true
secondLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
secondLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
The problem is that for some reason the scrollview is not able to calculate it's height and it doesn't scroll vertically. The second label remains invisible because it's too "low" on the screen.
I tried setting the bottom constraint of the container view to the bottom of the second label but I'm going nowhere honestly.
What am I missing? How can I set constraints to make the scrollview scroll with autolayout (without setting a specific height of the container view)?
Scrollview scrolls in either direction. So any view added to it would need some additional constraints to let the Scrollview calculate its content size.
You have pinned the container view to scroll view. If you are looking at a vertically scrolling view like a table view then you need to also set the width of the scroll to the container view so that it can calculate the width.
Next for height, its automatically calculated based on the UI elements added in the container view. Ensure that all of the labels have leading, trailing to the container and vertical spacing to each other and top of the container view. This will let the scroll view know the height needed.
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.label1.translatesAutoresizingMaskIntoConstraints = false
self.label2.translatesAutoresizingMaskIntoConstraints = false
self.label3.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.label1)
self.contentView.addSubview(self.label2)
self.contentView.addSubview(self.label3)
self.scrollView.addSubview(self.contentView)
self.view.addSubview(self.scrollView)
NSLayoutConstraint.activate([
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor)
])
NSLayoutConstraint.activate([
self.contentView.topAnchor.constraint(equalTo: self.scrollView.topAnchor),
self.contentView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor),
self.contentView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor),
self.contentView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor),
self.contentView.widthAnchor.constraint(equalTo: self.scrollView.widthAnchor)
])
NSLayoutConstraint.activate([
self.label1.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
self.label2.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
self.label3.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
self.label1.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
self.label2.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
self.label3.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor)
])
NSLayoutConstraint.activate([
self.label1.topAnchor.constraint(equalTo: self.contentView.topAnchor),
self.label2.topAnchor.constraint(equalTo: self.label1.bottomAnchor),
self.label3.topAnchor.constraint(equalTo: self.label2.bottomAnchor),
self.label3.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
])
And when run, it won't complain about any constraint issues either. Same can be done for horizontal scroll.
Thank you very much #GoodSp33d, you pointed me to the right direction.
I added the width constraint as you said like that:
containerView.widthAnchor.constraint(equalTo: mainScrollView.widthAnchor).isActive = true
And the key point was to update the bottom constraint after all:
containerView.bottomAnchor.constraint(equalTo: secondLabel.bottomAnchor).isActive = true
Here is the full working code:
mainScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mainScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
mainScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mainScrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: mainScrollView.topAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: mainScrollView.trailingAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: mainScrollView.bottomAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: mainScrollView.leadingAnchor).isActive = true
containerView.widthAnchor.constraint(equalTo: mainScrollView.widthAnchor).isActive = true
firstLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
firstLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 30).isActive = true
firstLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
firstLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
secondLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: 550).isActive = true
secondLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
secondLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
containerView.bottomAnchor.constraint(equalTo: secondLabel.bottomAnchor).isActive = true
Thanks for the help.

Resources