How to set Dynamically UIScrollView height in Swift 4 IOS? - ios

If the height of scrollview not set. then scrollview will not scrollable.
To make it scrollable. I set height statically in code like this.
scrollView.contentSize = CGSize(width: self.view.frame.size.width, height: 1000)
Is there any way i can set height dynamically??

If you wanted to do with storyboard then first go through this.
If you wanted to do by code as you done it code then first you have to calculate the inner content height programatically the use the below code.
scrollView.contentSize = CGSize(width: self.view.frame.size.width, height: contentHeight)
Still have any query then ask.

One option using AutoLayout is to make UIStackView a subview of the UIScrollView. Set the constraints (leading, trailing, top, bottom and width) of UIStackView to be equal to UIScrollView. Set the height of the subviews (alternatively make them dynamic to resize depending on their own subviews). Then add the subviews to the UIStackView by using addArrangedSubview and it will handle the constraints for you. The UIScrollView's contentSize will adapt to the UIStackView depending on it's subviews.
Don't forget to change the axis property of UIStackView to vertical.
Here is an example using NSLayoutAnchor:
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let redView = UIView()
redView.backgroundColor = .red
redView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
redView.heightAnchor.constraint(equalToConstant: 400.0)
])
let blueView = UIView()
blueView.backgroundColor = .blue
blueView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
blueView.heightAnchor.constraint(equalToConstant: 700.0)
])
let scrollView = UIScrollView()
scrollView.backgroundColor = .black
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(redView)
stackView.addArrangedSubview(blueView)
scrollView.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
self.view = view
}
If you don't want to use UIStackView, just add every subview directly to UIScrollView and set the constraints how they relate to each other.

Related

Vertical UIScrollView programmatically

I added some constraints but it scrolls horizontally, Where is the problem how can I manage it to scroll vertically? I tried to add some extra points to the width and height of contentSize separately but no results it keeps scrolling horizontally.
And I just realized second problem my fav button inside UIView started not working after I added ScrollView, however, button added to the UIView that subview of ContentView which inside of ScrollView.
Thanks in Advance!
private func setupScrollView() {
self.view.addSubview(scrollView)
scrollView.addSubview(contentView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + 1000)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo:self.view.leadingAnchor)
])
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
])
You need to set the height of the content for the UIScrollView in the viewWillLayoutSubviews:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.scrollView.contentSize.height = self.view.frame.height + 1000
}
And remove this line: scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + 1000)

(Swift 5) UIScrollView scrolls but none of the content scrolls (video included)

I'm trying to learn to build views without storyboard. I tried to build a scrollview. On that scrollview is a UISearchBar, a UIImageView with an image and a UILabel. It works but none of the content moves. The content is all just frozen in place like no matter how far I scroll the search bar will always be on top of the page. and the image on the bottom. I've attached a video to show what I mean. There's also a problem because none of the content is where I want it to be but that's another problem. I realize this is probably because I don't know enough about constraints and autolayout and building views without storyboards.
Here's the video
class HomePageViewController: UIViewController {
var searchedText: String = ""
let label = UILabel()
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Where are you going?"
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.barTintColor = .systemCyan
searchBar.searchTextField.backgroundColor = .white
searchBar.layer.cornerRadius = 5
return searchBar
}()
let homeImage: UIImageView = {
let homeImage = UIImageView()
homeImage.translatesAutoresizingMaskIntoConstraints = false
homeImage.clipsToBounds = true
return homeImage
}()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .systemMint
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
// setupLayout()
// tried this here doesn't do anything for me
}
func setupLayout() {
view.addSubview(scrollView)
self.scrollView.addSubview(searchBar)
homeImage.image = UIImage(named: "Treehouse")
self.scrollView.addSubview(homeImage)
label.text = "Inspiration for your next trip..."
self.scrollView.addSubview(label)
// not sure where this label is being added I want it to be underneath the image but it isn't t
let safeG = view.safeAreaLayoutGuide
let viewFrame = view.bounds
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: -10),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
searchBar.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 50.0),
searchBar.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.9),
searchBar.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
homeImage.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 150),
homeImage.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 1.1),
homeImage.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
homeImage.heightAnchor.constraint(equalToConstant: viewFrame.height/2),
label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100)
])
// was doing all this in viewDidLayoutSubviews but not sure if this is better place for it
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupLayout()
// tried this in viewDidLoad() and it didn't solve it.
}
}
any help would be appreciated
First, when constraining subviews in a UIScrollView, you should constrain them to the scroll view's Content Layout Guide. You're constraining them to the view's safe area layout guide, so they're never going to go anywhere.
Second, it's difficult to center subviews in a scroll view, because the scroll view can scroll both horizontally and vertically. So it doesn't really have a "center."
You can either put subviews in a stack view, or, quite common, use a UIView as a "content" view to hold the subviews. If you constrain that content view's Width to the scroll view's Frame Layout Guide width, you can then horizontally center the subviews.
Third, it can be very helpful to comment your constraints, so you know exactly what you expect them to do.
Here's a modified version of your posted code:
class HomePageViewController: UIViewController {
var searchedText: String = ""
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Where are you going?"
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.barTintColor = .systemCyan
searchBar.searchTextField.backgroundColor = .white
searchBar.layer.cornerRadius = 5
return searchBar
}()
let homeImage: UIImageView = {
let homeImage = UIImageView()
homeImage.translatesAutoresizingMaskIntoConstraints = false
homeImage.clipsToBounds = true
return homeImage
}()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .systemMint
// don't do this
//scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
setupLayout()
}
func setupLayout() {
view.addSubview(scrollView)
//homeImage.image = UIImage(named: "Treehouse")
homeImage.image = UIImage(named: "natureBKG")
label.text = "Inspiration for your next trip..."
// let's use a UIView to hold the "scroll content"
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
// give it a green background so we can see it
contentView.backgroundColor = .green
contentView.addSubview(searchBar)
contentView.addSubview(homeImage)
contentView.addSubview(label)
scrollView.addSubview(contentView)
let safeG = view.safeAreaLayoutGuide
let svContentG = scrollView.contentLayoutGuide
let svFrameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView to all 4 sides of view
// (generally, constrain to safe-area, but this is what you had)
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// constrain contentView to all 4 sides of scroll view's Content Layout Guide
contentView.topAnchor.constraint(equalTo: svContentG.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: svContentG.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: svContentG.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: svContentG.bottomAnchor, constant: 0.0),
// constrain contentView Width equal to scroll view's Frame Layout Guide Width
contentView.widthAnchor.constraint(equalTo: svFrameG.widthAnchor),
// constrain searchBar Top to contentView Top + 50
searchBar.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 50.0),
// constrain searchBar Width to 90% of contentView Width
searchBar.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.9),
// constrain searchBar centerX to contentView centerX
searchBar.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain homeImage Top to searchBar Bottom + 40
homeImage.topAnchor.constraint(equalTo: searchBar.bottomAnchor, constant: 40.0),
// constrain homeImage Width equal to contentView Width
homeImage.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0),
// constrain homeImage centerX to contentView centerX
homeImage.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain homeImage Height to 1/2 of scroll view frame Height
homeImage.heightAnchor.constraint(equalTo: svFrameG.heightAnchor, multiplier: 0.5),
// you probably won't get vertical scrolling yet, so increase the vertical space
// between the homeImage and the label by changing the constant
// from 100 to maybe 400
// constrain label Top to homeImage Bottom + 100
label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100.0),
// constrain label centerX to contentView centerX
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain label Bottom to contentView Bottom - 20
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0),
])
}
}

How to make UIView which is inside scrollview adapt to screen orientation when user changes screen from portrait to landscape in swift

How to make UIView which is inside scrollview adapt to screen orientation when user changes screen from portrait to landscape in swift?
var scrollView: UIScrollView = {
var scroll = UIScrollView()
scroll.translatesAutoresizingMaskIntoConstraints = false
return scroll
}()
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
scrollView.heightAnchor.constraint(equalToConstant: 500).isActive = true
for i in 0..<arr.count {
var contentView = UIView()
contentView.frame = CGRect(x: i * Int(view.bounds.size.width) + 10, y: 0, width: Int(view.bounds.size.width) - 20 , height: Int(view.frame.height))
scrollView.contentSize = CGSize(width: (view.frame.size.width * CGFloat((Double(i)+1))) ,height: scrollView.frame.size.height)
}
Image
You really want to be using auto-layout instead of trying to calculate frame sizes. Let it do all the work for you.
Based on your code, it looks like you want each "contentView" to be the width of the scrollView's frame, minus 20 (so you have 10-pts of space on each side).
You can quite easily do this by embedding your contentViews in a UIStackView.
Here's a simple example:
class ViewController: UIViewController {
var scrollView: UIScrollView = {
var scroll = UIScrollView()
scroll.translatesAutoresizingMaskIntoConstraints = false
return scroll
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
// use a stack view to hold and arrange the scrollView's subviews
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 20
// add the stackView to the scrollView
scrollView.addSubview(stackView)
// respect safe area
let safeG = view.safeAreaLayoutGuide
// use scrollView's Content Layout Guide to define scrollable content
let layoutG = scrollView.contentLayoutGuide
// use scrollView's Frame Layout Guide to define content height (since you want horizontal scrolling)
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 100),
// you're setting leading and trailing, so no need for centerX
//scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: 0),
// let's constrain the scrollView bottom to the view (safe area) bottom
//scrollView.heightAnchor.constraint(equalToConstant: 500),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -10.0),
// constrain Top and Bottom of the stackView to scrollView's Content Layout Guide
stackView.topAnchor.constraint(equalTo: layoutG.topAnchor),
stackView.bottomAnchor.constraint(equalTo: layoutG.bottomAnchor),
// 10-pts space on leading and trailing
stackView.leadingAnchor.constraint(equalTo: layoutG.leadingAnchor, constant: 10.0),
stackView.trailingAnchor.constraint(equalTo: layoutG.trailingAnchor, constant: -10.0),
// constrain stackView's height to scrollView's Frame Layout Guide height
stackView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
])
// add some views to the stack view
let arr: [UIColor] = [
.red, .green, .blue, .yellow, .purple,
]
for i in 0..<arr.count {
let contentView = UIView()
contentView.backgroundColor = arr[i]
stackView.addArrangedSubview(contentView)
// constrain each "contentView" width to scrollView's Frame Layout Guide width minus 20
contentView.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: -20).isActive = true
// don't do this
//contentView.frame = CGRect(x: i * Int(view.bounds.size.width) + 10, y: 0, width: Int(view.bounds.size.width) - 20 , height: Int(view.frame.height))
// don't do this
//scrollView.contentSize = CGSize(width: (view.frame.size.width * CGFloat((Double(i)+1))) ,height: scrollView.frame.size.height)
}
}
}
Run that and see if that's what you're going for.
You need to add your subviews to the scroll view and setup their constraints - using of an auto-layout. Don't use contentView.frame = CGRect(...) and scrollView.contentSize = CGSize(...).
For example you can change your for-in to this:
Note: this is only example, change your for-in loop to your needs.
for i in 0..<arr.count {
// we need to distinguish the first and last subviews (because different constraints)
let topAnchor = i == 0 ? scrollView.topAnchor : scrollView.subviews.last!
let isLast = i == arr.count - 1
// here we will use a specific height for all subviews except the last one
let subviewHeight = 60
var contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
if isLast {
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
]
} else {
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.heightAnchor.constraint(equalToConstant: subviewHeight),
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
]
}
}

Swift - Make whole view controller vertically scrollable: have empty space at bottom safe area

I have a MainViewController with some elements inside. I want the whole view to be vertically scrollable.
I made this BaseScrollableViewController class:
class BaseScrollableViewController: UIViewController, UIScrollViewDelegate {
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.delegate = self
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
return scrollView
}()
lazy var contentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(contentView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
])
}
}
Then I made the MainViewController a subclass of this BaseScrollableViewController. Inside, I use contentView to add all the subviews.
Now it scrolls vertically. However, when I scroll down to the bottom, here is what is shows:
Note that there is an empty space at the bottom.
I have tried the scrollView.contentInsetAdjustmentBehavior = .never, but then the scrollView becomes not scrollable.
So how can I fix this? Thanks!
For a scrollView to be scrollable, its subviews need to be bigger than the scrollView. By specifying the following constraint contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor) on the BaseScrollableViewController you are specifying that the contentView will have the same height of the scrollView, thus making the view not scrollable when setting scrollView.contentInsetAdjustmentBehavior = .never.
You can test this by adding a multiplier value on the contentView height constraint:
contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.5)
And enabling
`scrollView.contentInsetAdjustmentBehavior = .never`

addSubview and addArrangedSubview not showing content

I'm adding a UILabel to a UIView and then adding the UIView to a UIStackView, but the UILabel is not showing.
override func viewDidLoad() {
super.viewDidLoad()
configureStackView()
label.text = "test"
containerView.addSubview(label)
stackView.addArrangedSubview(containerView)
view.addSubview(stackView)
setStackViewConstraints()
}
func configureStackView() {
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 5
}
func setStackViewConstraints() {
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
I tried setting some sort of size for the view, but doesn't work:
containerView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
or setting constraints to the label to the containerView for widthAnchor, leadingAnchor, or trailingAnchor, but that doesn't work either.
UIView on its own does not have an intrinsicContentSize, which UIStackView uses to determine the size of the view when placed in the stackView. Either set constraints for the four edges of the label to the edges of the container
label.topAnchor.constraint(container.topAnchor, constant: myTopConstant).isActive = true
label.leading....
label.bottom....
label.trailing....
label.setContentHuggingPriority(to: .defaultLow, axis: .vertical)
(above, relax the contentHuggingPriority if you are not placing any other views in the stackView; since it has a fixed height and distribution = .fill, your stackView will try to stretch the arrangedViews to fit the height. If the label/container are the only candidates, you'll be breaking constraints with a .required contentHugging on the label)
or give the container a well-defined height (since the axis of your stackView is vertical)
containerView.heightAnchor.constraint(equalTo: ....).isActive = true
Also, in your container, set
translatesAutoresizing... = false

Resources