Reputation: 4044
Can anyone tell me why the subviews that I've added to my UIScrollView won't scroll? When I run the project I can scroll the UIScrollView up and down, I can see the scroll-indicator on the right side of the screen as I scroll. But the subviews that I've added to the UIScrollView stay in the same position as if they aren't subviews of the UIScrollView at all. This is very strange because I copied most of this code from another view i'm using and its working exactly as expect, just this view won't work . Thanks for looking, here's my code
import UIKit
class EnterCredentialsVC2: UIViewController {
private lazy var scrollView: UIScrollView = { [unowned self] in
let view = UIScrollView()
view.backgroundColor = UIColor.ricdBlue
view.contentSize.height = self.view.frame.size.height + 300
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let emailTextField: UITextField = {
let field = UITextField()
field.placeholder = "Email Address"
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
private let passwordTextField: UITextField = {
let field = UITextField()
field.placeholder = "Password (6 characters minimum)"
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
private let enterButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private let errorLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.minimumScaleFactor = 0.1
label.adjustsFontSizeToFitWidth = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
layoutView()
}
}
//view layout methods
extension EnterCredentialsVC2 {
private func layoutView() {
layoutScrollView()
layoutEmailTextField()
layoutPasswordTextField()
layoutEnterButton()
layoutErrorLabel()
}
private func layoutScrollView() {
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
private func layoutEmailTextField() {
scrollView.addSubview(emailTextField)
emailTextField.heightAnchor.constraint(equalToConstant: 45).isActive = true
emailTextField.widthAnchor.constraint(equalToConstant: view.bounds.width * 0.9).isActive = true
emailTextField.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
emailTextField.topAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true
}
private func layoutPasswordTextField() {
scrollView.addSubview(passwordTextField)
passwordTextField.heightAnchor.constraint(equalToConstant: 45).isActive = true
passwordTextField.widthAnchor.constraint(equalToConstant: view.bounds.width * 0.9).isActive = true
passwordTextField.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
passwordTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 50).isActive = true
}
private func layoutEnterButton() {
scrollView.addSubview(enterButton)
enterButton.widthAnchor.constraint(equalToConstant: view.bounds.width * 0.5).isActive = true
enterButton.heightAnchor.constraint(equalToConstant: 55).isActive = true
enterButton.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
let top = enterButton.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 50)
top.priority = .defaultHigh
top.isActive = true
let bottom = enterButton.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.bottomAnchor, constant: 200)
bottom.priority = .defaultLow
bottom.isActive = true
}
private func layoutErrorLabel() {
scrollView.addSubview(errorLabel)
errorLabel.widthAnchor.constraint(equalToConstant: view.bounds.width-20).isActive = true
errorLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
let top = errorLabel.topAnchor.constraint(equalTo: enterButton.bottomAnchor, constant: 30)
top.priority = .required
top.isActive = true
let bottom = errorLabel.bottomAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.bottomAnchor, constant: -20)
bottom.priority = .defaultLow
bottom.isActive = true
}
}
Upvotes: 0
Views: 362
Reputation: 4044
Found the source of the issue, here is the line that I changed:
private func layoutEmailTextField() {
...
emailTextField.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 100).isActive = true
}
constraining the textFields topAnchor
equal to scrollView.safeAreaLayoutGuide.topAnchor
is different than constraining it to the scrollView.topAnchor
. Here is the relevant documentation and also Here. My thoughts are that because the safeArea is any area that is not obscured by the device, the subviews were forced to stay within that area and therefore wouldn't scroll beyond it. Although I'm not sure why they wouldn't bounce around whatsoever, even within the safeArea... anybody who knows more on this please leave a comment, thanks.
Upvotes: 1
Reputation: 535989
Constraints of direct subviews inside a scroll view work in a special way. They must be pinned either to the scroll view itself or to its contentLayoutGuide
. This has a special meaning: "I'm not really pinned to your outside, I'm just showing you the layout of the subviews and I'm telling you your content size."
Pinning to the safe area layout guide — a thing that never moves — completely violates that. It's like pinning to the scroll view's frameLayoutGuide
, or to something outside the scroll view altogether. It specifically instructs the scroll view not to take these views into account when working out the content height.
You had explicitly given the scroll view a content size — in a wrong way at a wrong time, but that's a different matter — and "luckily" that caused the scroll view to be scrollable. But if you set up the internal constraints correctly, any explicit setting of the contentSize
is ignored.
Upvotes: 2