Swift textview growing direction - ios

so i have a textview inside a uiview. And my question is how to make my textivew grow in up direction when textview goes to the next line as well as my uiview.
var textheightcontraint : NSLayoutConstraint!
var viewheightconstraint : NSLayoutConstraint!
func setup4(){
view.addSubview(colorview)
colorview.topAnchor.constraint(equalTo: customtableview.bottomAnchor).isActive = true
colorview.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
colorview.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
viewheightconstraint = colorview.heightAnchor.constraint(equalToConstant: 44)
viewheightconstraint.isActive = true
colorview.backgroundColor = UIColor.lightGray
colorview.addSubview(customtextview2)
customtextview2.backgroundColor = .white
customtextview2.leftAnchor.constraint(equalTo: colorview.leftAnchor).isActive = true
customtextview2.rightAnchor.constraint(equalTo: colorview.rightAnchor, constant: -20).isActive = true
customtextview2.bottomAnchor.constraint(equalTo: colorview.bottomAnchor).isActive = true
textheightcontraint = customtextview2.heightAnchor.constraint(equalToConstant: 39)
textheightcontraint.isActive = true
customtextview2.delegate = self
}
func setuptextview(){
let fixedWidth = customtextview2.frame.size.width
let newSize = customtextview2.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
self.textheightcontraint.constant = newSize.height
self.viewheightconstraint.constant = newSize.height
self.view.layoutIfNeeded()
}
func textViewDidChange(_ textView: UITextView) {
setuptextview()
}

If I understand your question, you want the text view (and the view it's contained in) to keep its bottom at the same position, and expand upward as you type?
To do that, disable scrolling in the text view, and set up your constraints so the bottom of the containing view is constrained to a y-position:
//
// ViewController.swift
//
// Created by Don Mag on 8/9/18.
//
import UIKit
class ViewController: UIViewController {
var theContainingView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
var theTextView: UITextView = {
let v = UITextView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .yellow
theContainingView.backgroundColor = .cyan
theContainingView.addSubview(theTextView)
view.addSubview(theContainingView)
NSLayoutConstraint.activate([
theTextView.topAnchor.constraint(equalTo: theContainingView.topAnchor, constant: 8.0),
theTextView.bottomAnchor.constraint(equalTo: theContainingView.bottomAnchor, constant: -8.0),
theTextView.leadingAnchor.constraint(equalTo: theContainingView.leadingAnchor, constant: 8.0),
theTextView.trailingAnchor.constraint(equalTo: theContainingView.trailingAnchor, constant: -8.0),
theContainingView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40.0),
theContainingView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40.0),
// Constrain the Bottom of the containing view. As the textView grows (or shrinks) with input,
// the TOP of the view will move up or down
theContainingView.bottomAnchor.constraint(equalTo: view.topAnchor, constant: 300.0),
])
theTextView.isScrollEnabled = false
theTextView.text = "This is the starting text."
}
}
Results:

Related

Swift - Update auto layout properties of UI components programmatically

To design and create my UI, I always use auto layouts and do it programmatically instead of using storyboard.
In every view class of mine, I have a method called
private func setupView(frame:CGRect) {
/* START CONTAINER VIEW */
containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: frame.width * (13 / IPHONE8_SCREEN_WIDTH)).isActive = true
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -frame.width * (13 / IPHONE8_SCREEN_WIDTH)).isActive = true
containerView.topAnchor.constraint(equalTo: topAnchor, constant: frame.height * (26 / IPHONE8_SCREEN_HEIGHT)).isActive = true
containerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
containerView.backgroundColor = UIColor.white
/* END CONTAINER VIEW */
...
}
to initialize the components. Now let's say, in the method above, I initialize 10 UI components which are properly displayed when I run my code. However, depending on some variables, I have another function that is being called
private func addNextRoundInformation() {
..
nextRoundLabel = UILabel()
nextRoundLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(nextRoundLabel)
nextRoundLabel.leadingAnchor.constraint(equalTo: currentRoundLabel.leadingAnchor).isActive = true
nextRoundLabel.widthAnchor.constraint(equalTo:currentRoundLabel.widthAnchor).isActive = true
nextRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
}
which should place a new label between some others which were already initialized.
Of course, when putting the new label between some particular ones, I also update the auto layout constraints of the of the bottom label like
private func updateNumberOfWinnersLabelConstraint() {
numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: nextRoundLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: nextRoundLabelValue.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
}
The topAnchor of each label depends on the bottom anchor of the previous one.
With this approach, I can't see nextRoundLabel at all. It only appears, if I initialize it in the private func setupView(frame:CGRect) {}
Why?
You could do this with "Top-to-Bottom" constraints, but it would be rather complex.
You would need to essentially create a "Linked List" to track each view, the views above and below it, and its constraints.
So, to "insert" a new view after the 3rd view, you would need to:
deactivate the inter-view constraints
insert the new view into the linked list
re-create and activate the new constraints
Putting your views in a UIStackView turns that process into a single line of code:
stackView.insertArrangedSubview(newView, at: 3)
Here's a quick example:
class ViewController: UIViewController {
let testView: SampleView = SampleView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
let infoLabel: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.numberOfLines = 0
v.text = "Tap to Insert \"New Label\"\nafter \"Label 3\""
return v
}()
let btn: UIButton = {
let v = UIButton()
v.setTitle("Insert", for: [])
v.setTitleColor(.white, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
v.backgroundColor = .systemBlue
return v
}()
[infoLabel, btn, testView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
infoLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.75),
infoLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
btn.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0),
btn.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.75),
btn.centerXAnchor.constraint(equalTo: g.centerXAnchor),
testView.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 40.0),
testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
testView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
])
btn.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
}
#objc func btnTapped(_ sender: Any?) {
testView.insertNew()
}
}
class SampleView: UIView {
var containerView: UIView!
var stackView: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
backgroundColor = .red
setupView(frame: .zero)
}
private func setupView(frame:CGRect) {
/* START CONTAINER VIEW */
containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(stackView)
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 12),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 8.0),
stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8.0),
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8.0),
])
containerView.backgroundColor = UIColor.white
/* END CONTAINER VIEW */
// add 10 labels to the stack view
for i in 1...10 {
let v = UILabel()
v.text = "Label \(i)"
v.backgroundColor = .green
stackView.addArrangedSubview(v)
}
}
func insertNew() {
let v = UILabel()
v.text = "New Label"
v.backgroundColor = .cyan
v.translatesAutoresizingMaskIntoConstraints = false
// we're adding a label after 3rd label
stackView.insertArrangedSubview(v, at: 3)
}
}
It starts looking like this:
after tapping the "Insert" button, it looks like this:
Edit
To explain why your current approach isn't working...
Starting with this layout:
each label's Top is constrained to the previous label's Bottom (with constant: spacing).
Those constraints are indicated by the blue arrows.
You then want to "insert" Next Round Label between Round Ends In and Winners Per Round:
Your code:
adds the label
adds a constraint from the Top of Next Round Label to the Bottom of Round Ends In
adds a constraint from the Top of Winners Per Round to the Bottom of Next Round Label
but... Winners Per Round already has a .topAnchor connected to Round Ends In, so it now has two top anchors.
The conflicting constraints are shown in red:
As I said, I think your description of what you're trying to do would lend itself to using stack views, and would make "inserting" views so much easier.
But, if you need to stick with your current "Top-to-Bottom" constraints approach, you have several options.
One - remove all the labels, then re-add and re-constrain them, including the one you're inserting.
Two - track the constraints (using an array or custom object properties) so you can deactivate the conflicting constraint(s).
Three - use some code along the lines of
let theConstraint = containerView.constraints.first(where: {($0.secondItem as? UILabel) == roundEndsInLabel})
to "find" the constraint that needs to be deactivated.
I found a working solution:
First, I declared two helper variables
/* START HELPER VARIABLES */
var numberOfWinnersLabelTopConstraint:NSLayoutConstraint?
var numberOfWinnersLabelValueTopConstraint:NSLayoutConstraint?
/* END HELPER VARIABLES */
then I made some minor adjustments in my setupView function:
private func setupView(frame:CGRect) {
...
/* START NUMBER OF WINNERS PER ROUND LABEL */
numberOfWinnersPerRoundLabel = UILabel()
numberOfWinnersPerRoundLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(numberOfWinnersPerRoundLabel)
numberOfWinnersPerRoundLabel.leadingAnchor.constraint(equalTo: currentRoundLabel.leadingAnchor).isActive = true
numberOfWinnersPerRoundLabel.widthAnchor.constraint(equalTo:currentRoundLabel.widthAnchor).isActive = true
// numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true // Replacing with helper variable
numberOfWinnersLabelTopConstraint = numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT))
numberOfWinnersLabelTopConstraint?.isActive = true
numberOfWinnersPerRoundLabel.text = NSLocalizedString(NUMBER_OF_WINNERS_PER_ROUND_TEXT, comment: "")
numberOfWinnersPerRoundLabel.textColor = .darkGray
numberOfWinnersPerRoundLabel.adjustsFontSizeToFitWidth = true
numberOfWinnersPerRoundLabel.font = .systemFont(ofSize: 12)
/* END NUMBER OF WINNERS PER ROUND LABEL */
/* START NUMBER OF WINNERS PER ROUND LABEL VALUE */
numberOfWinnersPerRoundLabelValue = UILabel()
numberOfWinnersPerRoundLabelValue.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(numberOfWinnersPerRoundLabelValue)
numberOfWinnersPerRoundLabelValue.leadingAnchor.constraint(equalTo: currentRoundLabelValue.leadingAnchor).isActive = true
numberOfWinnersPerRoundLabelValue.widthAnchor.constraint(equalTo:currentRoundLabelValue.widthAnchor).isActive = true
// numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: numberOfWinnersPerRoundLabel.topAnchor).isActive = true // replacing with helper variable
numberOfWinnersLabelValueTopConstraint = numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: numberOfWinnersPerRoundLabel.topAnchor)
numberOfWinnersLabelValueTopConstraint?.isActive = true
numberOfWinnersPerRoundLabelValue.textColor = .black
/* END NUMBER OF WINNERS PER ROUND LABEL VALUE */
By introducing the helper variables, I could easily deactivate the topConstraint when adding the nextRoundLabel
private func addNextRoundInformation() {
nextRoundLabel = UILabel()
nextRoundLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(nextRoundLabel)
nextRoundLabel.leadingAnchor.constraint(equalTo: currentRoundLabel.leadingAnchor).isActive = true
nextRoundLabel.widthAnchor.constraint(equalTo:currentRoundLabel.widthAnchor).isActive = true
nextRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
nextRoundLabel.text = "Next round starts in"
nextRoundLabel.textColor = .darkGray
nextRoundLabel.font = .systemFont(ofSize: 12)
nextRoundLabelValue = UILabel()
nextRoundLabelValue.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(nextRoundLabelValue)
nextRoundLabelValue.leadingAnchor.constraint(equalTo: currentRoundLabelValue.leadingAnchor).isActive = true
nextRoundLabelValue.widthAnchor.constraint(equalTo:currentRoundLabelValue.widthAnchor).isActive = true
nextRoundLabelValue.topAnchor.constraint(equalTo:nextRoundLabel.topAnchor).isActive = true
nextRoundLabelValue.textColor = .black
nextRoundLabelValue.text = "Next round label value"
nextRoundLabelValue.font = .systemFont(ofSize: 14)
}
private func updateNumberOfWinnersLabelConstraint() {
numberOfWinnersLabelTopConstraint?.isActive = false // Deactivate previous constraint
numberOfWinnersLabelValueTopConstraint?.isActive = false
numberOfWinnersLabelTopConstraint = numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: nextRoundLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT))
numberOfWinnersLabelTopConstraint?.isActive = true
numberOfWinnersLabelValueTopConstraint = numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: nextRoundLabelValue.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT))
numberOfWinnersLabelValueTopConstraint?.isActive = true
}
Basically, I only had to update the topConstraints of the numberOfWinnersPerRoundLabel and numberOfWinnersPerRoundLabelValue
since everything else would be the same. No changes needed for currentRoundLabel.
I tested it and it worked!

Using ScrollView with StackView as subview and UIViews as children of StackView

I'm struggling to get my scroll view to work programatically.
I have a view controller that instantiates a UIScrollView with the following constraints
class HomeTabBarController: ViewController {
let homePageView = HomePageView()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
homePageView.setupHomePage()
view.addSubview(homePageView)
///constraints
homePageView.stackView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
homePageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
homePageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true;
homePageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true;
homePageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -83).isActive = true;
}
}
The HomePageView (UIScrollView) has a UIStackView and instantiates 3 more UIViews which are the UIStackView's children. The code and constraints are as follows
class HomePageView: UIScrollView {
var homePageCarrousel: HomePageCarrousel?
var homePageSocial: HomePageSocialUp?
var homePageAboutUs: HomePageAboutUs?
var stackView = UIStackView()
func setupHomePage() {
translatesAutoresizingMaskIntoConstraints = false
homePageCarrousel = HomePageCarrousel()
homePageSocial = HomePageSocialUp()
homePageAboutUs = HomePageAboutUs()
guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }
homePageSocial.setupSocialHeader()
stackView.addArrangedSubview(homePageSocial)
homePageCarrousel.setupCarrousel()
stackView.addArrangedSubview(homePageCarrousel)
homePageAboutUs.setup()
stackView.addArrangedSubview(homePageAboutUs)
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 10
setLayout()
}
func setLayout(){
guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }
///header
homePageSocial.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
homePageSocial.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
homePageSocial.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
///carrousel
homePageCarrousel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 20).isActive = true
homePageCarrousel.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 20).isActive = true
homePageCarrousel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -20).isActive = true
///about us
homePageAboutUs.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
homePageAboutUs.topAnchor.constraint(equalTo: homePageCarrousel.bottomAnchor, constant: 10).isActive = true
homePageAboutUs.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
homePageAboutUs.bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
///stackview
self.stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true;
self.stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true;
self.stackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true;
self.stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true;
}
func dispose() {
homePageSocial = nil
homePageCarrousel = nil
homePageAboutUs = nil
subviews.forEach{$0.removeFromSuperview()}
}
}
Each of the children UIView (homePageSocial, homePageCarrousel and homePageAboutUs) have constraints have also got constraints:
HomePageCarrousel
Loads UIImageView and after adding as subview sets the constraint as
heightAnchor.constraint(equalToConstant: imageView.frame.height).isActive = true
HomePageAboutUs
Has 3 UITextviews (headerText, bodyTextLeft, bodyTextRight)
Header on top, bodytextLeft below it being 50% width of screen x = 0 and bodyTextRight x = width of bodyTextLeft.
Constraints are as follows
heightAnchor.constraint(equalToConstant: headerText.frame.height + bodyTextLeft.frame.height).isActive = true
bodyTextLeft.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 15).isActive = true
bodyTextLeft.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width / 2).isActive = true
bodyTextRight.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 15).isActive = true
bodyTextRight.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width / 2).isActive = true
bodyTextRight.leadingAnchor.constraint(equalTo: bodyTextLeft.trailingAnchor).isActive = true
HomePageSocialUp
A basic header with an icon and a constraint of
heightAnchor.constraint(equalToConstant: 325).isActive = true
After all that set I still can't get my ui scroll view to scroll on the y axis leaving my text of homePageAboutUs below the tabBar and off screen.
What am I doing wrong here?
Thanks in advance
First - the main purpose of a UIStackView is to arrange its subviews, so it is wrong to add position constraints to those subviews.
Next, when adding subviews to the "root" view of a controller (such as your scroll view), be sure to constrain them to the Safe Area Layout Guide.
Third, constrain the content of your scroll view to its Content Layout Guide.
I'm kind of taking your descriptions and hoping I'm close to what you're going for here:
and after scrolling down:
Here is your code, modified to produce that result:
class HomeTabBarController: UIViewController {
let homePageView = HomePageView()
override func viewDidLoad() {
super.viewDidLoad()
homePageView.setupHomePage()
view.addSubview(homePageView)
// respect safe area
let g = view.safeAreaLayoutGuide
///constraints
NSLayoutConstraint.activate([
homePageView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
homePageView.topAnchor.constraint(equalTo: g.topAnchor),
homePageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
homePageView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
}
}
class HomePageSocialUp: UIView {
func setupSocialHeader() -> Void {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .red
let imgView = UIImageView()
imgView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imgView)
NSLayoutConstraint.activate([
// constrain image view 20-pts on each side
imgView.topAnchor.constraint(equalTo: topAnchor, constant: 20.0),
imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
imgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
imgView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20.0),
// Height = 325
imgView.heightAnchor.constraint(equalToConstant: 325.0),
])
if let img = UIImage(named: "myHeaderImage") {
imgView.image = img
}
}
}
class HomePageCarrousel: UIView {
func setupCarrousel() -> Void {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .green
let imgView = UIImageView()
imgView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imgView)
NSLayoutConstraint.activate([
// constrain image view 20-pts on each side
imgView.topAnchor.constraint(equalTo: topAnchor, constant: 20.0),
imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
imgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
imgView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20.0),
// let's make it 3:2 ratio
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: 2.0 / 3.0)
])
if let img = UIImage(named: "myCarouselImage") {
imgView.image = img
}
}
}
class HomePageAboutUs: UIView {
let headerText = UILabel()
let bodyTextLeft = UILabel()
let bodyTextRight = UILabel()
func setup() -> Void {
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .blue
[headerText, bodyTextLeft, bodyTextRight].forEach {
// keep label height to text content
$0.setContentHuggingPriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .vertical)
// allow word-wrap
$0.numberOfLines = 0
// yellow background
$0.backgroundColor = .yellow
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
NSLayoutConstraint.activate([
// header text 8-pts from Top / Leading / Trailing
headerText.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
headerText.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
headerText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
// left text 8-pts from Bottom of header text, 8-pts Leading
bodyTextLeft.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 8.0),
bodyTextLeft.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
// right text 8-pts from Bottom of header text, 8-pts Trailing
bodyTextRight.topAnchor.constraint(equalTo: headerText.bottomAnchor, constant: 8.0),
bodyTextRight.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
// 8-pts between left and right text
bodyTextRight.leadingAnchor.constraint(equalTo: bodyTextLeft.trailingAnchor, constant: 8.0),
// left and right text equal width
bodyTextLeft.widthAnchor.constraint(equalTo: bodyTextRight.widthAnchor),
// constrain Bottom of both to <= 8 (at least 8-pts
bodyTextLeft.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
bodyTextRight.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
])
headerText.font = .systemFont(ofSize: 20, weight: .regular)
bodyTextLeft.font = .systemFont(ofSize: 16, weight: .regular)
bodyTextRight.font = .systemFont(ofSize: 16, weight: .regular)
// texxt alignment
headerText.textAlignment = .center
bodyTextLeft.textAlignment = .left
bodyTextRight.textAlignment = .right
// some sample text
headerText.text = "This is the text for the About Us Header label. It will, of course, wrap onto multiple lines when needed, and auto-size it's height to fit the text."
bodyTextLeft.text = "Left label with\nembedded newlines\nso we can see it grow\nto fit the text.\nLine 5\nLine 6\nLine 7"
bodyTextRight.text = "Right label will wrap if needed. The one with the most lines will determine the bottom."
}
}
class HomePageView: UIScrollView {
var homePageCarrousel: HomePageCarrousel?
var homePageSocial: HomePageSocialUp?
var homePageAboutUs: HomePageAboutUs?
var stackView = UIStackView()
func setupHomePage() {
translatesAutoresizingMaskIntoConstraints = false
homePageCarrousel = HomePageCarrousel()
homePageSocial = HomePageSocialUp()
homePageAboutUs = HomePageAboutUs()
guard let homePageSocial = homePageSocial, let homePageCarrousel = homePageCarrousel, let homePageAboutUs = homePageAboutUs else { return }
homePageSocial.setupSocialHeader()
stackView.addArrangedSubview(homePageSocial)
homePageCarrousel.setupCarrousel()
stackView.addArrangedSubview(homePageCarrousel)
homePageAboutUs.setup()
stackView.addArrangedSubview(homePageAboutUs)
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 10
setLayout()
}
func setLayout(){
// constrain stackView to scroll view's Content Layout Guide
let g = self.contentLayoutGuide
///stackview
NSLayoutConstraint.activate([
self.stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
self.stackView.topAnchor.constraint(equalTo: g.topAnchor),
self.stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
self.stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
// stack view width is scroll view's frame layout guide width
stackView.widthAnchor.constraint(equalTo: self.frameLayoutGuide.widthAnchor),
])
}
}

Vertical ScrollView not scrolling (No Storyboard)

I need help creating a Scroll View without Storyboards. Here is my code for setting up the Scroll View; I'm not setting a contentSize of the Scroll View because I'd like the scroll view content size to be dynamic, dependent on the amount of text in the TextView. What I did instead, is I tried adding a 'contentView' to the Scroll View and added all my UI elements into the contentView. Any help would be appreciated.
import Foundation
import UIKit
import UITextView_Placeholder
class ComposerVC: UIViewController {
private var scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private var contentView: UIView = {
let content = UIView()
content.translatesAutoresizingMaskIntoConstraints = false
return content
}()
private var title: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
title.placeholder = "Untitled"
title.textColor = UIColor(hexString: "#50E3C2")
title.font = UIFont(name: "Rubik-BoldItalic", size: 32)
title.backgroundColor = .clear
title.isScrollEnabled = false
return title
}()
private var divider: UIView = {
let divider = UIView()
divider.translatesAutoresizingMaskIntoConstraints = false
divider.backgroundColor = UIColor(hexString: "#50E3C2")
return divider
}()
private var content: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
title.placeholder = "Begin writing here..."
title.textColor = .white
title.font = UIFont(name: "Avenir-Book", size: 15)
title.backgroundColor = .clear
title.isScrollEnabled = false
return title
}()
override func viewDidLoad() {
setupUI()
setupUIConstraints()
title.delegate = self
}
private func setupUI() {
view.backgroundColor = UIColor(hexString: "#131415")
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(title)
contentView.addSubview(divider)
contentView.addSubview(content)
}
private func setupUIConstraints() {
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
title.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 95).isActive = true
title.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35).isActive = true
title.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -35).isActive = true
divider.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 15).isActive = true
divider.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
divider.heightAnchor.constraint(equalToConstant: 1).isActive = true
divider.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.8).isActive = true
content.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 15).isActive = true
content.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35).isActive = true
content.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -35).isActive = true
}
}
extension ComposerVC: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
let fixedWidth = textView.frame.size.width
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
textView.frame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
}
}
A couple tips:
Don't use existing names for variables... with your code as-is, private var title: UITextView causes problems (title is already a view controller property).
Use var names that imply the object... e.g. titleTextView and contentTextView instead of title and content
During development - particularly when you're working on layout - give your UI elements contrasting background colors so you can easily see their frames at runtime.
When using code-created views, set .clipsToBounds = true ... if you don't see any subviews you've added, you know the frame / constraints are missing something.
I don't have your UITextView_Placeholder import, but that shouldn't affect anything here...
So, first, change your viewDidLoad() to this:
override func viewDidLoad() {
setupUI()
setupUIConstraints()
titleTextView.delegate = self
// contrasting colors during development
scrollView.backgroundColor = .red
titleTextView.backgroundColor = .yellow
contentTextView.backgroundColor = .green
divider.backgroundColor = .blue
contentView.backgroundColor = .cyan
}
When you run it, you should see (scroll view background is red, and this is without the placeholder stuff):
It looks correct, except there's no cyan-colored contentView.
Now, clip the subviews of contentView:
private var contentView: UIView = {
let content = UIView()
content.translatesAutoresizingMaskIntoConstraints = false
// add this line
content.clipsToBounds = true
return content
}()
The result:
Where did everything go? Well, we didn't see the cyan contentView and now we don't see any of its subviews ... If we use Debug View Hierarchy we can find out contentView has a Height of Zero.
Fix that by constraining the bottom of the second text view in setupUIConstraints() (I've renamed it to contentTextView instead of just content):
contentTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -95).isActive = true
and we get:
Now the Height of the cyan contentView is controlled by correctly setup constraints of its subviews.
As a side note: with constraints setup properly, and scrolling disabled for the text views, you do not need your:
extension ComposerVC: UITextViewDelegate {
//func textViewDidChange(_ textView: UITextView) {
//...
//}
}
The text view will automatically size itself to its text:
Here's the complete edited code:
class ComposerVC: UIViewController {
private var scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private var contentView: UIView = {
let content = UIView()
content.translatesAutoresizingMaskIntoConstraints = false
// add this line so we know if the constraints are set correctly
content.clipsToBounds = true
return content
}()
private var titleTextView: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
// title.placeholder = "Untitled"
title.textColor = UIColor(hexString: "#50E3C2")
title.font = UIFont(name: "Rubik-BoldItalic", size: 32)
title.backgroundColor = .clear
title.isScrollEnabled = false
return title
}()
private var divider: UIView = {
let divider = UIView()
divider.translatesAutoresizingMaskIntoConstraints = false
divider.backgroundColor = UIColor(hexString: "#50E3C2")
return divider
}()
private var contentTextView: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
// title.placeholder = "Begin writing here..."
title.textColor = .white
title.font = UIFont(name: "Avenir-Book", size: 15)
title.backgroundColor = .clear
title.isScrollEnabled = false
return title
}()
override func viewDidLoad() {
setupUI()
setupUIConstraints()
titleTextView.delegate = self
// contrasting colors during development
scrollView.backgroundColor = .red
titleTextView.backgroundColor = .yellow
contentTextView.backgroundColor = .green
divider.backgroundColor = .blue
contentView.backgroundColor = .cyan
}
private func setupUI() {
view.backgroundColor = UIColor(hexString: "#131415")
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(titleTextView)
contentView.addSubview(divider)
contentView.addSubview(contentTextView)
}
private func setupUIConstraints() {
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
titleTextView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 95).isActive = true
titleTextView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35).isActive = true
titleTextView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -35).isActive = true
divider.topAnchor.constraint(equalTo: titleTextView.bottomAnchor, constant: 15).isActive = true
divider.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
divider.heightAnchor.constraint(equalToConstant: 1).isActive = true
divider.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.8).isActive = true
contentTextView.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 15).isActive = true
contentTextView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35).isActive = true
contentTextView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -35).isActive = true
contentTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -95).isActive = true
}
}
extension ComposerVC: UITextViewDelegate {
// func textViewDidChange(_ textView: UITextView) {
// let fixedWidth = textView.frame.size.width
// let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
// textView.frame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
// }
}
Assuming you are using iOS 11+, Your contentView should have its anchors constrained to the contentLayoutGuide of the scrollView. like this:
contentView
.leadingAnchor
.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor).isActive = true
contentView
.topAnchor
.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor).isActive = true
contentView
.trailingAnchor
.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor).isActive = true
contentView
.bottomAnchor
.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor).isActive = true
Also, its width should be constrained to the scrollView's frameLayoutGuide and not the view's width, like this:
contentView
.widthAnchor
.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor).isActive = true
That should make the scrollView detect the content size to fit.

How to set content hugging and compression programmatically

I want to animate hide show when one of my view is hidden. so I'm using content hugging priority to animate that, but it failed it has a gap between view. here I show you the ui and my code
This is 3 uiview code like the picture above
scrollView.addSubview(chooseScheduleDropDown)
chooseScheduleDropDown.translatesAutoresizingMaskIntoConstraints = false
chooseScheduleDropDown.setContentCompressionResistancePriority(.required, for: .vertical)
NSLayoutConstraint.activate([
chooseScheduleDropDown.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
chooseScheduleDropDown.topAnchor.constraint(equalTo: scrollView.topAnchor),
chooseScheduleDropDown.widthAnchor.constraint(equalToConstant: 285),
chooseScheduleDropDown.heightAnchor.constraint(equalToConstant: 60)
])
scrollView.addSubview(entryView)
entryView.isHidden = true
entryView.setContentHuggingPriority(.defaultLow, for: .vertical)
entryView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
entryView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
entryView.topAnchor.constraint(equalTo: chooseScheduleDropDown.bottomAnchor, constant: topPadding),
entryView.widthAnchor.constraint(equalToConstant: 285),
entryView.heightAnchor.constraint(equalToConstant: 60)
])
scrollView.addSubview(chooseDateView)
chooseDateView.setContentHuggingPriority(.defaultLow, for: .vertical)
chooseDateView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
chooseDateView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
chooseDateView.topAnchor.constraint(equalTo: entryView.bottomAnchor, constant: topPadding),
chooseDateView.widthAnchor.constraint(equalToConstant: 285),
chooseDateView.heightAnchor.constraint(equalToConstant: 60)
])
After exchanging comments, you have a number of different tasks to work on.
But, to give you an example of one approach to showing / hiding the "middle" view and having the bottom view move up / down, here is something to try. It will look like this:
Tapping the top (red) view will hide the middle (green) view and slide the bottom (blue) view up. Tapping the top (red) view again will slide the bottom (blue) view down and show the middle (green) view.
This is done by creating two top constraints for the Bottom view. One relative to the bottom of the Top view, and the other relative to the bottom of the Middle view, with different .priority values.
The example code is fairly straight-forward, and the comments should make things clear. All done via code - no #IBOutlet or #IBAction connections - so just create a new view controller and assign its custom class to AnimTestViewController:
class DropDownView: UIView {
}
class AnimTestViewController: UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
return v
}()
let chooseScheduleDropDown: DropDownView = {
let v = DropDownView()
return v
}()
let entryView: DropDownView = {
let v = DropDownView()
return v
}()
let chooseDateView: DropDownView = {
let v = DropDownView()
return v
}()
var visibleConstraint: NSLayoutConstraint = NSLayoutConstraint()
var hiddenConstraint: NSLayoutConstraint = NSLayoutConstraint()
override func viewDidLoad() {
super.viewDidLoad()
[chooseScheduleDropDown, entryView, chooseDateView].forEach {
v in
v.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(v)
}
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
let g = view.safeAreaLayoutGuide
let topPadding: CGFloat = 20.0
// chooseDateView top anchor when entryView is visible
visibleConstraint = chooseDateView.topAnchor.constraint(equalTo: entryView.bottomAnchor, constant: topPadding)
// chooseDateView top anchor when entryView is hidden
hiddenConstraint = chooseDateView.topAnchor.constraint(equalTo: chooseScheduleDropDown.bottomAnchor, constant: topPadding)
// we will start with entryView visible
visibleConstraint.priority = .defaultHigh
hiddenConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
chooseScheduleDropDown.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
chooseScheduleDropDown.topAnchor.constraint(equalTo: scrollView.topAnchor),
chooseScheduleDropDown.widthAnchor.constraint(equalToConstant: 285),
chooseScheduleDropDown.heightAnchor.constraint(equalToConstant: 60),
entryView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
entryView.topAnchor.constraint(equalTo: chooseScheduleDropDown.bottomAnchor, constant: topPadding),
entryView.widthAnchor.constraint(equalToConstant: 285),
entryView.heightAnchor.constraint(equalToConstant: 60),
chooseDateView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
//chooseDateView.topAnchor.constraint(equalTo: entryView.bottomAnchor, constant: topPadding),
visibleConstraint,
hiddenConstraint,
chooseDateView.widthAnchor.constraint(equalToConstant: 285),
chooseDateView.heightAnchor.constraint(equalToConstant: 60),
])
//entryView.isHidden = true
chooseScheduleDropDown.backgroundColor = .red
entryView.backgroundColor = .green
chooseDateView.backgroundColor = .blue
let tap = UITapGestureRecognizer(target: self, action: #selector(toggleEntryView(_:)))
chooseScheduleDropDown.addGestureRecognizer(tap)
}
#objc func toggleEntryView(_ sender: UITapGestureRecognizer) -> Void {
print("tapped")
// if entryView IS hidden we want to
// un-hide entryView
// animate alpha to 1.0
// animate chooseDateView down
// if entryView is NOT hidden we want to
// animate alpha to 0.0
// animate chooseDateView up
// hide entryView when animation is finished
let animSpeed = 0.5
if entryView.isHidden {
entryView.isHidden = false
hiddenConstraint.priority = .defaultLow
visibleConstraint.priority = .defaultHigh
UIView.animate(withDuration: animSpeed, animations: {
self.entryView.alpha = 1.0
self.view.layoutIfNeeded()
}, completion: { _ in
})
} else {
visibleConstraint.priority = .defaultLow
hiddenConstraint.priority = .defaultHigh
UIView.animate(withDuration: animSpeed, animations: {
self.entryView.alpha = 0.0
self.view.layoutIfNeeded()
}, completion: { _ in
self.entryView.isHidden = true
})
}
}
}
Do
// declare an instance property
var hCon:NSLayoutConstraint!
// get only the height constraint out of the activate block
hCon = entryView.heightAnchor.constraint(equalToConstant: 60)
hCon.isActive = true
and play with
hCon.constant = 300 / 0
view.layoutIfNeeded()

Using ScrollView Programmatically in Swift 3

I have searched other questions and seem to still have some trouble creating my scrollView programmatically with autolayout in swift 3. I am able to get my scrollview to show up as shown in the picture below, but when I scroll to the bottom my other label does not show up and the 'scroll top' label does not disappear.
Hoping someone can help review my code below!
import UIKit
class ViewController: 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
}()
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.contentSize = CGSize(width: screenWidth, height: 2000)
scrollView.addSubview(labelOne)
scrollView.addSubview(labelTwo)
view.addSubview(labelOne)
view.addSubview(labelTwo)
view.addSubview(scrollView)
// Visual Format Constraints
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": labelOne]))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-100-[v0]", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": labelOne]))
// Using iOS 9 Constraints in order to place the label past the iPhone 7 view
view.addConstraint(NSLayoutConstraint(item: labelTwo, attribute: .top, relatedBy: .equal, toItem: labelOne, attribute: .bottom, multiplier: 1, constant: screenHeight + 200))
view.addConstraint(NSLayoutConstraint(item: labelTwo, attribute: .right, relatedBy: .equal, toItem: labelOne, attribute: .right, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: labelTwo, attribute: .left, relatedBy: .equal, toItem: labelOne, attribute: .left, multiplier: 1, constant: 0)
}
}
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 UIKit
import PlaygroundSupport
class 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 = .yellow
PlaygroundPage.current.liveView = vc
Edit - since this answer still gets occasional attention, I've updated the code to use more modern syntax, to respect the safe-area, and to use the scroll view's .contentLayoutGuide:
class TestViewController : UIViewController {
let labelOne: UILabel = {
let label = UILabel()
label.text = "Scroll Top"
label.backgroundColor = .yellow
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)
// add labelOne to the scroll view
scrollView.addSubview(labelOne)
// add labelTwo to the scroll view
scrollView.addSubview(labelTwo)
// always a good idea to respect safe area
let safeG = view.safeAreaLayoutGuide
// we want to constrain subviews to the scroll view's Content Layout Guide
let contentG = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// constrain the scroll view to safe area with 8-pts on each side
scrollView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 8.0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 8.0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -8.0),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -8.0),
// constrain labelOne to leading & top of Content Layout Guide with 16-pts padding
// this also defines the left & top of the scroll content
labelOne.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 16.0),
labelOne.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 16.0),
// constrain labelTwo leading at 400-pts from labelOne trailing
labelTwo.leadingAnchor.constraint(equalTo: labelOne.trailingAnchor, constant: 400.0),
// constrain labelTwo top at 1000-pts from the labelOne bottom
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 1000),
// constrain labelTwo to trailing & bottom of Content Layout Guide with 16-pts padding
// this also defines the right & bottom of the scroll content
labelTwo.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: -16.0),
labelTwo.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: -16.0),
])
}
}
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
}
}
Set scrollview images to the wallpaper:
#IBOutlet var scroll_view_img: UIScrollView!
var itemPhotoList = NSMutableArray()
var button = NSMutableArray()
#IBOutlet var imageview_big: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
itemPhotoList = ["grief-and-loss copy.jpg","aaa.jpg","image_4.jpeg"]
// button = ["btn1","btn2"]
let width:CGFloat = 100
let height:CGFloat = 100
var xposition:CGFloat = 10
var scroll_contont:CGFloat = 0
for i in 0 ..< itemPhotoList.count
{
var button_img = UIButton()
button_img = UIButton(frame: CGRect(x: xposition, y: 50, width: width, height: height))
let img = UIImage(named:itemPhotoList[i] as! String)
button_img.setImage(img, for: .normal)
scroll_view_img.addSubview(button_img)
button_img.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button_img.tag = i
view.addSubview(scroll_view_img)
xposition += width+10
scroll_contont += width
scroll_view_img.contentSize = CGSize(width: scroll_contont, height: height)
}
}
func buttonAction(sender: UIButton!)
{
switch sender.tag {
case 0:
imageview_big.image = UIImage(named: "grief-and-loss copy.jpg")
case 1:
imageview_big.image = UIImage(named: "aaa.jpg")
case 2:
imageview_big.image = UIImage(named: "image_4.jpeg")
default:
break
}
}
Copy and paste this controller in your project
class BaseScrollViewController: UIViewController {
lazy var contentViewSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + 100)
lazy var scrollView: UIScrollView = {
let view = UIScrollView(frame: .zero)
view.backgroundColor = .white
view.frame = self.view.bounds
view.contentSize = contentViewSize
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var containerView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.frame.size = contentViewSize
return v
}()
override func viewDidLoad() {
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.addSubview(containerView)
setupContainer(containerView)
super.viewDidLoad()
}
public func setupContainer(_ container: UIView) {
}
}
Usage for above code:
class ClientViewController: BaseScrollViewController {
override func viewDidLoad() {
super.viewDidLoad()
// do your stuff here
}
override func setupContainer(_ container: UIView) {
// add views here
}
}
These answers do not work with large titles in the navigation bar. Make sure you have the code below in your viewDidLoad() method of your view controller:
self.navigationController?.navigationBar.prefersLargeTitles = false

Resources