Using Scrollview Programmatically in Swift 3

Using ScrollView Programmatically in Swift 3

It is easy to use constraints to define the scroll content size - so you don't have to do any manual calculations.

Just remember:

  1. 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.
  2. 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.
  3. 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),

])

}

}

Create a vertical UIScrollView programmatically in Swift

Strictly I feel problem is at below line.

self.view = scollView

It should be self.view.addSubview(scollView)

Then add all label, buttons, etc in scrollview and then give content size.

Content size is the parameter that will tell scrollview to scroll.

Put self.scrollView.contentSize = CGSize(width:2000, height: 5678) inside viewDidLayoutSubviews.

UIScrollView Programmatically in Swift

Your code is working perfectly, but there is a small problem in adding buttonOne on the view.

You have added buttonOne on self.view (i.e subview of self.view), not containerView which is on scrollView.

You should use the following code

containerView.addSubview(buttonOne)

instead of

self.view.addSubview(buttonOne)

Add subviews programmatically to ScrollView using Visual Format Language

each loop round, you add a right side pin constraint to scroll view for every new
added imageView, but there is 20 points between each other.

 |-previous-20-newOne-|                                ** second round loop **
-20-newOne'-| ** third round loop **

this breaks imageView width(240) constraint

one way deal with it:
only add right side pin constraint to the last imageView.

Sample Image

Sample Image

scroll view constraint in your main storyboard also has a break.

+-------------------+
| | |
|-scroll view (240)-|

the bottom one with vertical spacing to super view should not be there. it would
break the scroll view height(240), so delete it will be fine.

maybe you should try:

  • set it's constraint priority to 999, or some other value not equal to
    1000
  • uncheck installed box
  • delete it

Sample Image


and now, your scroll view should be OK.

Sample Image


p.s. I found your reference book is based on iOS 6? in the year 2018, starts from iOS 10 or iOS 11 may be a better choice.

happy hacking :)

Add buttons to UIScrollView Swift 5 programmatically

You really should be using Auto-Layout!!!

By far, the easiest way to add buttons (or any view) to a scroll view - in a grid layout like you're asking - and have it automatically determine the scrolling area is with stack views.

Here's a quick example:

class ViewController: UIViewController {

let label = UILabel()
let scrollView = UIScrollView()

override func viewDidLoad() {
super.viewDidLoad()

// start with some text in the label
label.text = "Tap a button"

// center the text in the label
label.textAlignment = .center

// so we can see the frames
label.backgroundColor = .yellow
scrollView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)

// create a vertical stack view to hold the rows of buttons
let verticalStackView = UIStackView()
verticalStackView.axis = .vertical

// we're going to use auto-layout
label.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.translatesAutoresizingMaskIntoConstraints = false

// add label to view
self.view.addSubview(label)

// add Scrollview to view
self.view.addSubview(scrollView)

// add stack view to scrollView
scrollView.addSubview(verticalStackView)

// now let's create the buttons and add them
var idx = 1

for row in 0...20 {
// create a "row" stack view
let rowStack = UIStackView()
// add it to the vertical stack view
verticalStackView.addArrangedSubview(rowStack)

for col in 0...4 {
let button = UIButton()
button.backgroundColor = .gray
button.setTitle("\(idx)", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)

// add button to row stack view
rowStack.addArrangedSubview(button)

// buttons should be 50x50
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 50.0),
button.heightAnchor.constraint(equalToConstant: 50.0),
])

idx += 1
}
}

// finally, let's set our constraints

// respect safe-area
let safeG = view.safeAreaLayoutGuide

NSLayoutConstraint.activate([

// constrain label
// 50-pts from top
// 80% of the width
// centered horizontally
label.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 50.0),
label.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.8),
label.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),

// constrain scrollView
// 50-pts from bottom of label
// Leading and Trailing to safe-area with 8-pts "padding"
// Bottom to safe-area with 8-pts "padding"
scrollView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 50.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 vertical stack view to scrollView Content Layout Guide
// 8-pts all around (so we have a little "padding")
verticalStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 8.0),
verticalStackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 8.0),
verticalStackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -8.0),
verticalStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -8.0),

])

}

@objc func buttonAction(sender: UIButton!) {
label.text = sender.title(for: .normal)
}

}

and this is the result:

Sample Image

then we'll scroll down and tap a different button:

Sample Image

If you want spacing between the buttons, you can add something like this at the end of viewDidLoad():

// suppose we want 8-pts spacing between the buttons?
verticalStackView.spacing = 8.0
verticalStackView.arrangedSubviews.forEach { v in
if let stack = v as? UIStackView {
stack.spacing = 8.0
}
}

Now it looks like this:

Sample Image

Sample Image

If you want the grid of buttons centered horizontally, or if you want the spacing (or button sizes) to adjust to fit, there are a few different ways to do that... You didn't describe your ultimate goal in your question, but this should be a good starting point.

ios Swift: ScrollView with dynamic content programmatic layout

A UIScrollView is designed to automatically allow scrolling when its content is larger than its frame.

By itself, a scroll view has NO intrinsic size. It doesn't matter how many subviews you add to it... if you don't do something to set its frame, its frame size will always be .zero.

If we want to get the scroll view's frame to grow in height based on its content we need to give it a height constraint when the content size changes.

If we want it to scroll when it has a lot of content, we also need to give it a maximum height.

So, if we want MyView height to be max of 1/2 the screen (view) height, we constrain its height (in the controller) like this:

myView.heightAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.heightAnchor, multiplier: 0.5)

and then constrain the scroll view height in MyView like this:

let svh = scrollView.heightAnchor.constraint(equalToConstant: scrollView.contentSize.height)
svh.priority = .required - 1
svh.isActive = true

Here is a modification to your code - lots of comments in the code so you should be able to follow.

First, an example controller:

class MVTestVC: UIViewController {

let myView = MyView()

let sampleStrings: [String] = [
"Short string.",
"This is a longer string which should wrap onto a couple lines.",
"Now let's use a really, really long string. This will make the label taller, but still not enough to require vertical scrolling.",
"We want to see what happens when we DO need scrolling.\n\nSo, let's use a long string, with some embedded newlines.\n\nThis will make the label tall enough that it would exceed one-half the screen height, so we can see that we do, in fact, get vertical scrolling.",
]
var strIndex: Int = 0

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([

// 20-points on each side
myView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
myView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),

// centered vertically
myView.centerYAnchor.constraint(equalTo: g.centerYAnchor),

// max 1/2 screen (view) height
myView.heightAnchor.constraint(lessThanOrEqualTo: g.heightAnchor, multiplier: 0.5),

])

myView.backgroundColor = .white
myView.configure(with: sampleStrings[0])
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
strIndex += 1
myView.configure(with: sampleStrings[strIndex % sampleStrings.count])
}
}

and the modified MyView class:

public final class MyView: UIView {

private let titleLabel = UILabel()
private let scrollView = UIScrollView()

// this will be used to set the scroll view height
private var svh: NSLayoutConstraint!

override public init(frame: CGRect) {

super.init(frame: frame)

let closeButton = UIButton(type: .system)
closeButton.translatesAutoresizingMaskIntoConstraints = false
//(button setup)
closeButton.setTitle("X", for: [])
closeButton.backgroundColor = UIColor(white: 0.9, alpha: 1.0)

scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.showsVerticalScrollIndicator = false
scrollView.alwaysBounceVertical = false

titleLabel.translatesAutoresizingMaskIntoConstraints = false
//(label's font and alignment setup)
titleLabel.font = .systemFont(ofSize: 24.0, weight: .light)
titleLabel.numberOfLines = 0

let successButton = UIButton(type: .system)
successButton.translatesAutoresizingMaskIntoConstraints = false
//(button setup)
successButton.setTitle("Success", for: [])
successButton.backgroundColor = UIColor(white: 0.9, alpha: 1.0)

addSubview(closeButton)
addSubview(scrollView)
addSubview(successButton)
scrollView.addSubview(titleLabel)

let layoutGuide = UILayoutGuide()
addLayoutGuide(layoutGuide)

let contentLayoutGuide = scrollView.contentLayoutGuide

NSLayoutConstraint.activate([
layoutGuide.leadingAnchor.constraint(equalToSystemSpacingAfter: leadingAnchor, multiplier: 2),
trailingAnchor.constraint(equalToSystemSpacingAfter: layoutGuide.trailingAnchor, multiplier: 2),

layoutGuide.topAnchor.constraint(equalToSystemSpacingBelow: topAnchor, multiplier: 2),
bottomAnchor.constraint(equalToSystemSpacingBelow: layoutGuide.bottomAnchor, multiplier: 2),

closeButton.leadingAnchor.constraint(greaterThanOrEqualTo: layoutGuide.leadingAnchor),
layoutGuide.trailingAnchor.constraint(greaterThanOrEqualTo: closeButton.trailingAnchor),
closeButton.centerXAnchor.constraint(equalTo: layoutGuide.centerXAnchor),
closeButton.topAnchor.constraint(equalTo: layoutGuide.topAnchor),
closeButton.heightAnchor.constraint(equalToConstant: 33),

scrollView.topAnchor.constraint(equalTo: closeButton.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: successButton.topAnchor),

successButton.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
layoutGuide.trailingAnchor.constraint(equalTo: successButton.trailingAnchor),
successButton.heightAnchor.constraint(equalToConstant: 48),
layoutGuide.bottomAnchor.constraint(equalTo: successButton.bottomAnchor),

// constrain the label to the scroll view's Content Layout Guide
titleLabel.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor, constant: 16),
titleLabel.leadingAnchor.constraint(equalTo: contentLayoutGuide.leadingAnchor, constant: 16),
titleLabel.trailingAnchor.constraint(equalTo: contentLayoutGuide.trailingAnchor, constant: -16),
titleLabel.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor, constant: -16),

// label needs a width anchor, otherwise we'll get horizontal scrolling
titleLabel.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: -32),
])

layer.cornerRadius = 12

// so we can see the framing
scrollView.backgroundColor = .red
titleLabel.backgroundColor = .green
}

public override func layoutSubviews() {
super.layoutSubviews()

// we want to update the scroll view's height constraint when the text changes
if let c = svh {
c.isActive = false
}
// on initial layout, the scroll view's content size will still be zero
// so force another layout pass
if scrollView.contentSize.height == 0 {
scrollView.setNeedsLayout()
scrollView.layoutIfNeeded()
}
// constrain the scroll view's height to the height of its content
// but with a less-than-required priority so we can use a maximum height
svh = scrollView.heightAnchor.constraint(equalToConstant: scrollView.contentSize.height)
svh.priority = .required - 1
svh.isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

//public func configure(with viewModel: someViewModel) {
// titleLabel.text = viewModel.title
//}
public func configure(with str: String) {
titleLabel.text = str
// force the scroll view to update its layout
scrollView.setNeedsLayout()
scrollView.layoutIfNeeded()
// force self to update its layout
self.setNeedsLayout()
self.layoutIfNeeded()
}
}

Each tap anywhere on the screen will cycle through a few sample strings to change the text in the label, giving us this:

Sample Image

Sample Image

Sample Image

Sample Image

Sample Image

Sample Image

UIScrollView not scrolling at all when added programmatically in Swift?


view.contentSize = CGSize(width: self.view.bounds.width, height: self.view.bounds.height * 2)

You should try to log the contentSize in your console after trying to access it. I am not sure if you are setting the correct contentSize here if the self.view.bounds has been calculated correctly when this gets called at that moment. Since it takes time for self.view frame and bounds to be calculated.

Try setting your contentSize after you have added the actual content to it based on the actual total content size.

EDIT:

Add a single UIView inside the scrollView, with the constraints set to top-bottom-leading-trailing, and add your subviews to it. Also, set the same constraints on the scrollView to the superView top-bottom-leading-trailing.

Programmatically display UIScrollView in Swift

you are setting the bounds of the scrollView to the size of the whole view with this code: scrollView.frame = view.bounds.

The scrollView only needs to scroll the content in the bottom view. Scroll Views have their own content, that is normally larger than the viewable area of the screen/view. The scroll view just allows you to pan the viewport of that view.

So add the bottom view and setup your constraints on that. add the scrollView to the bottom view and then add your content into the scrollView.

Make sure that your bottom view has clipToBounds set to true and then you should be able to keep the headers in place and just scroll the content.

I'll try and put an example together for you shortly.

EDIT:

I've just created this simple example which shows the scroll behaviour you need. This works in a playground or just as a simple view controller. I've intentionally not used auto layout or setup constraints due to time, but you will see what you need to solve your issue

class ViewController: UIViewController {

var topView: UIView!
var bottomView: UIView!
var scrollView: UIScrollView!
var contentView: UIView!

override func viewDidLoad() {
super.viewDidLoad()

let screenSize = UIScreen.main.bounds

self.topView = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: 100))
self.bottomView = UIView(frame: CGRect(x: 0, y: 100, width: screenSize.width, height: screenSize.height - 100))
self.scrollView = UIScrollView(frame: CGRect(x: 0, y: 100, width: screenSize.width, height: screenSize.height - 100))
self.contentView = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height * 3))

self.view.backgroundColor = .white
self.view.addSubview(self.topView)
self.view.addSubview(self.bottomView)
self.bottomView.addSubview(self.scrollView)
self.scrollView.addSubview(self.contentView)
self.bottomView.clipsToBounds = true


self.scrollView.contentSize = CGSize(width: screenSize.width, height: screenSize.height * 3)
self.contentView.backgroundColor = .gray
}
}


Related Topics



Leave a reply



Submit