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

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),
])
}
}

Related

Combining constraints with variable and fixed heights on UIView in Swift

Update:
Thanks everyone for the unique approaches. Appreciate your insights.
Background:
I have written some code below in Xcode Swift 5 that creates four equal sized rectangles that resize depending on the device size and orientation.
However, the desired result is a rectangle with different heights, where the top rectangles are variable heights depending on the device size and orientation, and the bottom rectangles are with a constant height of 200px.
Question:
What changes to my code do I need to make to achieve variable heights on top and fixed heights on bottom?
Code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
view.addSubview(frameTopLeft)
frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
frameTopLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
view.addSubview(frameTopRight)
frameTopRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
frameTopRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
view.addSubview(frameBottomLeft)
frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
view.addSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
}
}
Images:
Simulator output.
Desired output.
Use StackView for better code and understanding.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let topStack = UIStackView()
topStack.axis = .horizontal
topStack.distribution = .fillEqually
let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
topStack.addArrangedSubview(frameTopLeft)
let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
topStack.addArrangedSubview(frameTopRight)
let bottomStack = UIStackView()
bottomStack.axis = .horizontal
bottomStack.distribution = .fillEqually
let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
bottomStack.addArrangedSubview(frameBottomLeft)
let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
bottomStack.addArrangedSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
frameBottomRight.heightAnchor.constraint(equalToConstant: 200).isActive = true
let mainStack = UIStackView()
mainStack.axis = .vertical
mainStack.addArrangedSubview(topStack)
mainStack.addArrangedSubview(bottomStack)
self.view.addSubview(mainStack)
mainStack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: view.topAnchor),
mainStack.heightAnchor.constraint(equalTo: view.heightAnchor),
mainStack.leftAnchor.constraint(equalTo: view.leftAnchor),
mainStack.widthAnchor.constraint(equalTo: view.widthAnchor)
])
}
}
Or you can give relative constraints to each bottom view.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
view.addSubview(frameTopLeft)
frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
// frameTopLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5), << Here
frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
view.addSubview(frameTopRight)
frameTopRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
// frameTopRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5), << Here
frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
view.addSubview(frameBottomLeft)
frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomLeft.topAnchor.constraint(equalTo: frameTopLeft.bottomAnchor), // << Here
frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomLeft.heightAnchor.constraint(equalToConstant: 200), // << Here
frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
view.addSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomRight.topAnchor.constraint(equalTo: frameTopRight.bottomAnchor), // << Here
frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomRight.heightAnchor.constraint(equalToConstant: 200), // << Here
frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
])
}
}
You want to give the Bottom views constant Height constraints of 200, and then constrain the Bottoms of the top views to the Tops of the Bottom views.
You may find it helpful to group related code together, and add comments as you go (to make it a little clearer what you believe the code should do).
So, try it like this:
override func viewDidLoad() {
super.viewDidLoad()
// create 4 views
let frameTopLeft = UIView()
let frameTopRight = UIView()
let frameBottomLeft = UIView()
let frameBottomRight = UIView()
// set background colors
frameTopLeft.backgroundColor = .systemRed
frameTopRight.backgroundColor = .systemBlue
frameBottomLeft.backgroundColor = .systemGreen
frameBottomRight.backgroundColor = .systemYellow
// set Autoresizing and add to view
[frameTopLeft, frameTopRight, frameBottomLeft, frameBottomRight].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
NSLayoutConstraint.activate([
// top-left view constrained Top / Left / 50% Width
frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
// top-right view constrained Top / Right / 50% Width
frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
// bottom-left view constrained Bottom / Left / 50% Width
frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
// bottom-right view constrained Bottom / Right / 50% Width
frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
// bottom views, constant Height of 200-pts
frameBottomLeft.heightAnchor.constraint(equalToConstant: 200.0),
frameBottomRight.heightAnchor.constraint(equalToConstant: 200.0),
// constrain bottoms of top views to tops of bottom views
frameTopLeft.bottomAnchor.constraint(equalTo: frameBottomLeft.topAnchor),
frameTopRight.bottomAnchor.constraint(equalTo: frameBottomRight.topAnchor),
])
}
I'd do it like this:
let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
view.addSubview(frameTopLeft)
frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
])
let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
view.addSubview(frameTopRight)
frameTopRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameTopRight.leftAnchor.constraint(equalTo: frameTopLeft.rightAnchor),
frameTopRight.widthAnchor.constraint(equalTo: frameTopLeft.widthAnchor)
])
let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
view.addSubview(frameBottomLeft)
frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomLeft.topAnchor.constraint(equalTo: frameTopLeft.bottomAnchor),
frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
frameBottomLeft.heightAnchor.constraint(equalToConstant: 200),
])
let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
view.addSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
frameBottomRight.topAnchor.constraint(equalTo: frameTopRight.bottomAnchor),
frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
frameBottomRight.leftAnchor.constraint(equalTo: frameBottomLeft.rightAnchor),
frameBottomRight.heightAnchor.constraint(equalTo: frameBottomLeft.heightAnchor),
frameBottomRight.widthAnchor.constraint(equalTo: frameBottomLeft.widthAnchor),
])
The main diff from others is getting rid of constants as much as possible.
Your bottom views should be 200? Add single 200 constraint, and make second view height be equal to the first one.
Instead of making width equal 50% of parent, you can make left width be equal right one
It makes you code easier to maintain, decreasing places to edit.

UIScrollview does not show all subviews and is not scrollable

I have some problems with scrollview. I added a ScrollView to my ViewController with simple UIViews. But the ScrollView does not scroll and it does not show all my subviews.
I followed this example IOS swift scrollview programmatically but somehow my code does not work. Here is my example
import UIKit
class StatisticsViewController: UIViewController{
let scrollView: UIScrollView = {
let view = UIScrollView()
view.backgroundColor = UIColor.lightGray.adjust(by: 28)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let topstatsView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}()
let resultsView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemPink
return view
}()
let blue: UIView = {
let view = UIView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let yellow: UIView = {
let view = UIView()
view.backgroundColor = .yellow
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(scrollView)
// constraints of scroll view
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.addSubview(topstatsView)
scrollView.addSubview(resultsView)
scrollView.addSubview(blue)
scrollView.addSubview(yellow)
NSLayoutConstraint.activate([
topstatsView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 40),
topstatsView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 30),
topstatsView.heightAnchor.constraint(equalToConstant: 250),
topstatsView.rightAnchor.constraint(equalTo: scrollView.rightAnchor)
])
NSLayoutConstraint.activate([
resultsView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 330),
resultsView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 30),
resultsView.heightAnchor.constraint(equalToConstant: 400),
resultsView.widthAnchor.constraint(equalToConstant: 450)
])
NSLayoutConstraint.activate([
blue.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 330),
blue.leftAnchor.constraint(equalTo: resultsView.rightAnchor, constant: 20),
blue.heightAnchor.constraint(equalToConstant: 400),
blue.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -30)
])
NSLayoutConstraint.activate([
yellow.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 800),
yellow.leadingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 20),
yellow.heightAnchor.constraint(equalToConstant: 400),
yellow.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -30)
])
}
Here is a screenshot of my example.
As you can see the red view (topstatsView) does not confirm the right anchor and you cannot see the yellow and blue ones. And it is not scrollable. I am not able to see my mistakes. Thanks in advance!
Here you define wrong constraint.
1) Always add constraints related to each views top, bottom, leading and trailing, instead of define top constraint of all views to a scrollview.
2) Its not a good practise to add both leading and trailing anchor when you already define a width anchor.
3) Add bottom constraint related to a scrollview bottom to make it scrollable.
4) Add leading and trailing constraint related to outerView instead of adding it related to a scrollview.
Here is the updated code:-
class StatisticsViewController: UIViewController{
let scrollView: UIScrollView = {
let view = UIScrollView()
view.backgroundColor = UIColor.lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let topstatsView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}()
let resultsView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemPink
return view
}()
let blue: UIView = {
let view = UIView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let yellow: UIView = {
let view = UIView()
view.backgroundColor = .yellow
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(scrollView)
// constraints of scroll view
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.addSubview(topstatsView)
scrollView.addSubview(resultsView)
scrollView.addSubview(blue)
scrollView.addSubview(yellow)
NSLayoutConstraint.activate([
topstatsView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 40),
topstatsView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 30),
topstatsView.heightAnchor.constraint(equalToConstant: 250),
topstatsView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -30)
])
NSLayoutConstraint.activate([
resultsView.topAnchor.constraint(equalTo: topstatsView.bottomAnchor, constant: 30),
resultsView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30),
resultsView.heightAnchor.constraint(equalToConstant: 400),
resultsView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -30)
])
NSLayoutConstraint.activate([
blue.topAnchor.constraint(equalTo: resultsView.bottomAnchor, constant: 30),
blue.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 20),
blue.heightAnchor.constraint(equalToConstant: 400),
blue.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -30)
])
NSLayoutConstraint.activate([
yellow.topAnchor.constraint(equalTo: blue.bottomAnchor, constant: 30),
yellow.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20),
yellow.heightAnchor.constraint(equalToConstant: 400),
yellow.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -30),
yellow.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor, constant: -20)
])
}
}

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()

IOS swift scrollview programmatically

I am implementing a scrollview with multiple views in it programmatically. When I add subviews to it, they are not been displayed.
Here is a full example of adding 3 UIView subviews to a UIScrollView, using constraints to define the scroll view's .contentSize.
You can run this directly in a Playground page - including the ability to scroll:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let redView: UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let greenView: UIView = {
let v = UIView()
v.backgroundColor = .green
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let blueView: UIView = {
let v = UIView()
v.backgroundColor = .blue
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
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 three views to the scroll view
scrollView.addSubview(redView)
scrollView.addSubview(greenView)
scrollView.addSubview(blueView)
// give each view a height of 300
NSLayoutConstraint.activate([
redView.heightAnchor.constraint(equalToConstant: 300),
greenView.heightAnchor.constraint(equalToConstant: 300),
blueView.heightAnchor.constraint(equalToConstant: 300),
])
// give each view a width constraint equal to scrollView's width
NSLayoutConstraint.activate([
redView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
greenView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
blueView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
])
// constrain each view's leading and trailing to the scrollView
// this also defines the width of the scrollView's .contentSize
NSLayoutConstraint.activate([
redView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
greenView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
blueView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
redView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
greenView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
blueView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
])
// constrain redView's Top to scrollView's Top + 8-pts padding
// this also defines the Top of the scrollView's .contentSize
NSLayoutConstraint.activate([
redView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8.0),
])
// constrain greenView's Top to redView's Bottom + 20-pts spacing
NSLayoutConstraint.activate([
greenView.topAnchor.constraint(equalTo: redView.bottomAnchor, constant: 20.0),
])
// constrain blueView's Top to greenView's Bottom + 20-pts spacing
NSLayoutConstraint.activate([
blueView.topAnchor.constraint(equalTo: greenView.bottomAnchor, constant: 20.0),
])
// constrain blueView's Bottom to scrollView's Bottom + 8-pts padding
// this also defines the Bottom / Height of the scrollView's .contentSize
// Note: it must be negative
NSLayoutConstraint.activate([
blueView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -8.0),
])
// result:
// scrollView's .contentSize.width is now
// scrollView's width (defined by subviews' leading and trailing anchors
//
// and scrollView's .contentSize.height is now
// redView-Height + 20-pts-spacing +
// greenView-Height + 20-pts-spacing +
// blueView-Height +
// 8-pts top-padding + 8-pts bottom-padding
// or 956
}
}
let vc = TestViewController()
vc.view.backgroundColor = .yellow
PlaygroundPage.current.liveView = vc

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