Using ScrollView Programmatically in Swift 3
It is easy to use constraints to define the scroll content size - so you don't have to do any manual calculations.
Just remember:
- The content elements of your scroll view must have left / top / width / height values. In the case of objects such as labels, they have intrinsic sizes, so you only have to define the left & top.
- The content elements of your scroll view also define the bounds of the scrollable area - the
contentSize
- but they do so with the bottom & right constraints. - Combining those two concepts, you see that you need a "continuous chain" with at least one element defining the top / left / bottom / right extents.
Here is a simple example, that will run directly in a Playground page:
import UIKitimport PlaygroundSupportclass TestViewController : UIViewController { let labelOne: UILabel = { let label = UILabel() label.text = "Scroll Top" label.backgroundColor = .red label.translatesAutoresizingMaskIntoConstraints = false return label }() let labelTwo: UILabel = { let label = UILabel() label.text = "Scroll Bottom" label.backgroundColor = .green label.translatesAutoresizingMaskIntoConstraints = false return label }() let scrollView: UIScrollView = { let v = UIScrollView() v.translatesAutoresizingMaskIntoConstraints = false v.backgroundColor = .cyan return v }() override func viewDidLoad() { super.viewDidLoad() // add the scroll view to self.view self.view.addSubview(scrollView) // constrain the scroll view to 8-pts on each side scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true // add labelOne to the scroll view scrollView.addSubview(labelOne) // constrain labelOne to left & top with 16-pts padding // this also defines the left & top of the scroll content labelOne.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16.0).isActive = true labelOne.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16.0).isActive = true // add labelTwo to the scroll view scrollView.addSubview(labelTwo) // constrain labelTwo at 400-pts from the left labelTwo.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 400.0).isActive = true // constrain labelTwo at 1000-pts from the top labelTwo.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 1000).isActive = true // constrain labelTwo to right & bottom with 16-pts padding labelTwo.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -16.0).isActive = true labelTwo.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -16.0).isActive = true }}let vc = TestViewController()vc.view.backgroundColor = .yellowPlaygroundPage.current.liveView = vc
Two things.
1. Add the labels to scroll view, not your view
You want your label to scroll with scroll view, then you should not add it on your view. When running your code, you can scroll but the fixed label there is pinned to your view, not on your scroll view
2. Make sure you added your constraints correctly
Try it on your storyboard about what combination of constraint is enough for a view. At least 4 constraints are needed for a label.
Bottom line
Here is a modified version of your code. For constraint I added padding left, padding top, width and height and it works. My code is
let labelOne: UILabel = { let label = UILabel() label.text = "Scroll Top" label.backgroundColor = .red label.translatesAutoresizingMaskIntoConstraints = false return label}()let labelTwo: UILabel = { let label = UILabel() label.text = "Scroll Bottom" label.backgroundColor = .green label.translatesAutoresizingMaskIntoConstraints = false return label}()override func viewDidLoad() { super.viewDidLoad() let screensize: CGRect = UIScreen.main.bounds let screenWidth = screensize.width let screenHeight = screensize.height var scrollView: UIScrollView! scrollView = UIScrollView(frame: CGRect(x: 0, y: 120, width: screenWidth, height: screenHeight)) scrollView.addSubview(labelTwo) NSLayoutConstraint(item: labelTwo, attribute: .leading, relatedBy: .equal, toItem: scrollView, attribute: .leadingMargin, multiplier: 1, constant: 10).isActive = true NSLayoutConstraint(item: labelTwo, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 200).isActive = true NSLayoutConstraint(item: labelTwo, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .topMargin, multiplier: 1, constant: 10).isActive = true NSLayoutConstraint(item: labelTwo, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30).isActive = true scrollView.contentSize = CGSize(width: screenWidth, height: 2000) view.addSubview(scrollView)}
And the scroll view looks like this
For me this work like charm
class WithDrawConfirmationViewController: UIViewController, WithDrawConfirmationViewProtocol { var presenter: WithDrawConfirmationPresenterProtocol? private let viewbackgroundColor = UIColor(hexString: "#F5F5F8") // MARK:- View properties lazy var scrollView: UIScrollView = { let scrollView = UIScrollView() //view.backgroundColor = .red scrollView.translatesAutoresizingMaskIntoConstraints = false return scrollView }() lazy var contentView: UIView = { let view = UIView() //view.backgroundColor = .green view.translatesAutoresizingMaskIntoConstraints = false return view }() lazy var headerView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() private lazy var titleLabel:UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.textColor = UIColor.themeBlack label.text = "Withdraw Confirmation"//"Loading.." label.textAlignment = .center label.font = UIFont(name: "AvenirNext-DemiBold", size: 20) label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping return label }() private lazy var descriptionLabel:UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.textColor = UIColor.themeGray label.text = "Please confirm your fixed deposit details before withdrawing your deposit"// "Loading.." label.textAlignment = .center label.font = UIFont(name: "AvenirNext-Medium", size: 14) label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping label.setContentHuggingPriority(1000, for: .vertical) return label }() private lazy var instructionLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.textColor = UIColor.themeGray label.text = "Complete fixed deposit will be withdrawn and the money will be debited to your account within 1-2 working days."//"Loading.." label.textAlignment = .left label.font = UIFont(name: "AvenirNext-Medium", size: 14) label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping return label }() private lazy var warningLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.textColor = UIColor.themeBlue label.text = "Axis bank will levy a penalty of 1% - 2% for premature withdrawal"//"Loading.." label.textAlignment = .left label.font = UIFont(name: "AvenirNext-DemiBold", size: 13) label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping return label }() private lazy var warningView: UIView = { let view = UIView() view.cornerradius = 5 view.backgroundColor = UIColor(hexFromString: "#E8F4FD") view.translatesAutoresizingMaskIntoConstraints = false return view }() private lazy var fdInformationDetailView: FDPurchaseDetailView = { let view = FDPurchaseDetailView(isPoweredByViewVisible: false) //view.backgroundColor = .red view.translatesAutoresizingMaskIntoConstraints = false return view }() private lazy var footerCTAView: FooterButtonViewView = { let view = FooterButtonViewView() view.translatesAutoresizingMaskIntoConstraints = false return view }() // MARK:- Life cycle override func viewDidLoad() { super.viewDidLoad() contentView.backgroundColor = viewbackgroundColor scrollView.backgroundColor = viewbackgroundColor setUpUI() presenter?.viewDidLoadTriggered() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() } private func setUpUI() { view.addSubview(scrollView) scrollView.addSubview(contentView) view.backgroundColor = viewbackgroundColor addFooter() setUpHeaderView() setUpFDDetailView() setUpInformationAndWarningView() //scrollView.backgroundColor = .lightGray scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true scrollView.bottomAnchor.constraint(equalTo: footerCTAView.topAnchor).isActive = true scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 100, right: 0) contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true //contentView.bottomAnchor.constraint(equalTo: footerCTAView.topAnchor).isActive = true } private func setUpHeaderView() { contentView.addSubview(headerView) headerView.addSubviews([titleLabel, descriptionLabel]) headerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true headerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true headerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4).isActive = true titleLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 8).isActive = true titleLabel.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true titleLabel.topAnchor.constraint(equalTo: headerView.topAnchor, constant: 8).isActive = true descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20).isActive = true descriptionLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 8).isActive = true descriptionLabel.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true descriptionLabel.bottomAnchor.constraint(equalTo: headerView.bottomAnchor, constant: -20).isActive = true } private func setUpFDDetailView() { contentView.addSubview(fdInformationDetailView) fdInformationDetailView.topAnchor.constraint(equalTo: headerView.bottomAnchor).isActive = true fdInformationDetailView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true fdInformationDetailView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true } private func setUpInformationAndWarningView() { contentView.addSubview(instructionLabel) instructionLabel.topAnchor.constraint(equalTo: fdInformationDetailView.bottomAnchor, constant: 20).isActive = true instructionLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true instructionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true warningView.addSubview(warningLabel) warningLabel.topAnchor.constraint(equalTo: warningView.topAnchor, constant: 8).isActive = true warningLabel.leadingAnchor.constraint(equalTo: warningView.leadingAnchor, constant: 20).isActive = true warningLabel.trailingAnchor.constraint(equalTo: warningView.trailingAnchor, constant: -20).isActive = true warningLabel.bottomAnchor.constraint(equalTo: warningView.bottomAnchor, constant: -8).isActive = true contentView.addSubview(warningView) warningView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 8).isActive = true warningView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true warningView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true //warningView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true warningView.bottomAnchor.constraint(greaterThanOrEqualTo: contentView.bottomAnchor, constant: -20).isActive = true } private func addFooter() { view.addSubview(footerCTAView) footerCTAView.delegate = self footerCTAView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true footerCTAView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true footerCTAView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true }}