Content in UIScrollView not scrolling - swift

I have a UIScrollView in my UIViewController. I can scroll the ScrollView (I can see the bar at the bottom of the scrollView moving), but the content I put as a subView into the ScrollView is not moving. Anybody have an idea, what I am doing wrong?
This is my ScrollView:
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
This is how I call it in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
self.scrollView.contentSize = CGSize(width:2000, height: 5678)
scrollView.isScrollEnabled = true
self.view.addSubview(scrollView)
scrollView.anchor()
scrollView.backgroundColor = .red
let priceLabelStack = UIStackView(arrangedSubviews: [price1Stack, price2Stack, price3Stack, price4Stack])
priceLabelStack.axis = .horizontal
priceLabelStack.spacing = 30
scrollView.addSubview(priceLabelStack)
priceLabelStack.anchor(top: stack.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 20, paddingLeft: 30, paddingRight: 30)
}
And this is the LayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
}
So the content (priceLabelStack) is not moving..

priceLabelStack.translatesAutoresizingMaskIntoConstraints = false
priceLabelStack.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
priceLabelStack.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
priceLabelStack.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
priceLabelStack.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
This was missing in my viewDidLoad(). Everything is working now

Related

How to remove mysterious top margin in UIStackView when `isLayoutMarginsRelativeArrangement=true`?

I'm creating a simple "toolbar" component with a horizontal axis UIStackView. It looks fine, except when I switch on isLayoutMarginsRelativeArrangement, a strange margin is added to the top above the items, making the height of the stack view incorrect.
I've tried giving the stack view directionalLayoutMargins property many different values, including no value at all. Yet still this unwanted spacing remains. Why does this margin exist and how can I remove it?
override func viewDidLoad() {
super.viewDidLoad()
self.stackView = UIStackView(frame: CGRect.zero)
self.stackView.axis = .horizontal
self.stackView.alignment = .center
self.stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
self.stackView.isLayoutMarginsRelativeArrangement = true
self.stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(self.stackView)
NSLayoutConstraint.activate([
self.stackView.topAnchor.constraint(equalTo: view.topAnchor),
self.stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
self.stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
self.stackView.heightAnchor.constraint(equalTo: view.heightAnchor)
])
let title = UILabel(frame: .zero)
title.text = "My Toolbar"
self.stackView.addArrangedSubview(title)
title.sizeToFit()
let button = MDCButton()
button.setTitle("Recipes", for: .normal)
button.applyContainedTheme(withScheme: containerScheme)
button.minimumSize = CGSize(width: 64, height: 48)
self.stackView.addArrangedSubview(button)
}
Here's a couple things to try...
First, StackBarViewControllerA which creates a newView (plain UIView) to hold the stack view with the label and button:
class StackBarViewControllerA: UIViewController {
var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
let newView = UIView()
newView.translatesAutoresizingMaskIntoConstraints = false
newView.backgroundColor = .cyan
view.addSubview(newView)
self.stackView = UIStackView(frame: CGRect.zero)
self.stackView.axis = .horizontal
self.stackView.alignment = .center
self.stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
self.stackView.isLayoutMarginsRelativeArrangement = true
self.stackView.translatesAutoresizingMaskIntoConstraints = false
newView.addSubview(self.stackView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
self.stackView.topAnchor.constraint(equalTo: newView.topAnchor),
self.stackView.bottomAnchor.constraint(equalTo: newView.bottomAnchor),
self.stackView.widthAnchor.constraint(equalTo: newView.widthAnchor),
self.stackView.heightAnchor.constraint(equalTo: newView.heightAnchor),
newView.topAnchor.constraint(equalTo: g.topAnchor),
newView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
newView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
])
let title = UILabel(frame: .zero)
title.text = "My Toolbar"
self.stackView.addArrangedSubview(title)
title.sizeToFit()
let button = UIButton() // MDCButton()
button.setTitle("Recipes", for: .normal)
button.backgroundColor = .blue
button.heightAnchor.constraint(equalToConstant: 48).isActive = true
//button.applyContainedTheme(withScheme: containerScheme)
//button.minimumSize = CGSize(width: 64, height: 48)
self.stackView.addArrangedSubview(button)
}
}
Second, StackBarViewControllerB using a custom StackBarView:
class StackBarView: UIView {
var stackView: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
self.stackView = UIStackView(frame: CGRect.zero)
self.stackView.axis = .horizontal
self.stackView.alignment = .center
self.stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
self.stackView.isLayoutMarginsRelativeArrangement = true
self.stackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.stackView)
NSLayoutConstraint.activate([
self.stackView.topAnchor.constraint(equalTo: self.topAnchor),
self.stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.stackView.widthAnchor.constraint(equalTo: self.widthAnchor),
self.stackView.heightAnchor.constraint(equalTo: self.heightAnchor)
])
let title = UILabel(frame: .zero)
title.text = "My Toolbar"
self.stackView.addArrangedSubview(title)
title.sizeToFit()
let button = UIButton() // MDCButton()
button.setTitle("Recipes", for: .normal)
button.backgroundColor = .blue
//button.applyContainedTheme(withScheme: containerScheme)
//button.minimumSize = CGSize(width: 64, height: 48)
button.widthAnchor.constraint(greaterThanOrEqualToConstant: 64).isActive = true
button.heightAnchor.constraint(greaterThanOrEqualToConstant: 48).isActive = true
button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
self.stackView.addArrangedSubview(button)
}
}
class StackBarViewControllerB: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = StackBarView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
view.addSubview(v)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: g.topAnchor),
v.leadingAnchor.constraint(equalTo: g.leadingAnchor),
v.trailingAnchor.constraint(equalTo: g.trailingAnchor),
])
}
}
Both give this result (I gave it a cyan background so we can see the frame):
Looks like the problem is your anchor settings. Try removing your bottomAnchor and set your heightAnchor to the size you want for your buttons plus some padding, instead of just matching the heightAnchor of view:
NSLayoutConstraint.activate([
self.stackView.topAnchor.constraint(equalTo: view.topAnchor),
// self.stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
self.stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
// self.stackView.heightAnchor.constraint(equalTo: view.heightAnchor),
self.stackView.heightAnchor.constraint(equalToConstant: 80)
])
Note: You may need to set an offset constant for your top anchor like: self.stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50)

Scrollview without NavigationController

I have no idea why I cannot add a working scroll view without embedding the VC in a navigation controller.
Here is my code for a VC which I open from a tab bar controller and it's not embedded in a navigation controller:
lazy var contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView(frame: view.bounds)
scrollView.backgroundColor = .white
scrollView.frame = self.view.bounds
scrollView.contentSize = contentSize
scrollView.autoresizingMask = UIView.AutoresizingMask.flexibleHeight
scrollView.bounces = true
return scrollView
}()
lazy var containerView : UIView = {
let view = UIView()
view.backgroundColor = .white
view.frame.size = contentSize
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupElements()
}
func setupElements() {
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(containerView)
let stackView = UIStackView()
containerView.addSubview(stackView)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 12
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.topAnchor, constant: 60).isActive = true
stackView.leadingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
}
I have a bunch of textfields and buttons in the stackview and they show up fine but the view does not scroll (vertically). What am I doing wrong?
You need to calculate the content size
Ex.
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + 100)
Also, try to consolidate your layout. Try using Autolayout
Your scrollView Content size should be bigger than your scrollView frame to make it scroll
scrollView.contentSize = contentSize

How to animate elements of a UIStackView that’s created in a separate function

I'm trying to animate a stackview and its elements upon pressing a UIButton. the problem is that the stackview is created inside a function which is called in viewdidload.
Because the stackview is created inside a function I cannot call it inside the function that manages the animation... I think i'm doing something wrong...
my stackview creation function
fileprivate func variableContent() {
let stackviewButtons = UIStackView(arrangedSubviews: [registerSerialNumberButton, dothislaterButton])
stackviewButtons.axis = .vertical
stackviewButtons.alignment = .fill
stackviewButtons.spacing = 10
let stackview = UIStackView(arrangedSubviews: [registerTitlelabel, registerdescriptionLabel, serialnumberInput, stackviewButtons])
stackview.axis = .vertical
stackview.spacing = 20
stackview.setCustomSpacing(40, after: registerdescriptionLabel)
stackview.setCustomSpacing(100, after: serialnumberInput)
view.addSubview(stackview)
stackview.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
stackview.centerXInSuperview()
stackview.centerYInSuperview()
}
viewdidload
override func viewDidLoad() {
variableContent()
}
You can return the stackview from your function and keep a reference to it as an instance variable. That way you can reuse it later.
class Controller: UIViewController {
var stackView: UIStackView!
fileprivate func variableContent() -> UIStackView {
let stackviewButtons = UIStackView(arrangedSubviews: [registerSerialNumberButton, dothislaterButton])
stackviewButtons.axis = .vertical
stackviewButtons.alignment = .fill
stackviewButtons.spacing = 10
let stackview = UIStackView(arrangedSubviews: [registerTitlelabel, registerdescriptionLabel, serialnumberInput, stackviewButtons])
stackview.axis = .vertical
stackview.spacing = 20
stackview.setCustomSpacing(40, after: registerdescriptionLabel)
stackview.setCustomSpacing(100, after: serialnumberInput)
view.addSubview(stackview)
stackview.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
stackview.centerXInSuperview()
stackview.centerYInSuperview()
return stackview
}
override func viewDidLoad() {
super.viewDidLoad()
self.stackView = variableContent()
}
}
An other option would be to give it a tag (stackview.tag = 42)
and recover it with self.view.viewWithTag(42)
In addition to Marcio's answer, you can also declare the stackView optionally and set it in the function:
class Controller: UIViewController {
var stackview: UIStackView?
fileprivate func variableContent() {
let stackviewButtons = UIStackView(arrangedSubviews: [registerSerialNumberButton, dothislaterButton])
stackviewButtons.axis = .vertical
stackviewButtons.alignment = .fill
stackviewButtons.spacing = 10
self.stackview = UIStackView(arrangedSubviews: [registerTitlelabel, registerdescriptionLabel, serialnumberInput, stackviewButtons])
stackview.axis = .vertical
stackview.spacing = 20
stackview.setCustomSpacing(40, after: registerdescriptionLabel)
stackview.setCustomSpacing(100, after: serialnumberInput)
view.addSubview(stackview)
stackview.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
stackview.centerXInSuperview()
stackview.centerYInSuperview()
}
// Whatever your animating function is:
func animateStackView() {
guard let stackview = self.stackview else { return }
// You now have access to your stackView for the rest of this function.
}
}
Note: For consistency with your question, I kept the name of the UIStackView as stackview in the code, but according to Swift naming conventions, you should name variables in camelcase (stackView)

Scroll view not scrolling horizontal

I have a custom view, i set it in parent like:
func setup(){
view.backgroundColor = .gray
view.addSubview(chartView)
chartView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
chartView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
chartView.topAnchor.constraint(equalTo: view.topAnchor, constant: statusAndNavigationBarHeight).isActive = true
chartView.heightAnchor.constraint(equalToConstant: Dimensions.chartHeight.value).isActive = true
}
Then in that view i tried to set up a scroll:
scroll = UIScrollView.init(frame: CGRect(x: 0.0, y: 0.0, width: scrollWidth(), // print 728.0
height: Double(Dimensions.chartHeight.value))) // print 400.0
scroll.isScrollEnabled = true
scroll.showsHorizontalScrollIndicator = true
addSubview(scroll)
}
And thats all, when i launch app i can't drag and scroll horizontally, in debug editor i can't see that it is scroll view here lying with large width.
The scrollView doesn't scroll with it's size , it needs a content that define it's content size for example
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let chartView = UIView()
chartView.translatesAutoresizingMaskIntoConstraints = false
chartView.backgroundColor = .red
view.backgroundColor = .gray
view.addSubview(chartView)
chartView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
chartView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
chartView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
chartView.heightAnchor.constraint(equalToConstant:200).isActive = true
let scroll = UIScrollView(frame: CGRect(x: 0.0,
y: 0.0,
width: UIScreen.main.bounds.size.width, // print 728.0
height: 200.0))
scroll.isScrollEnabled = true
scroll.showsHorizontalScrollIndicator = true
chartView.addSubview(scroll)
let www = UIView()
www.backgroundColor = .green
www.translatesAutoresizingMaskIntoConstraints = false
scroll.addSubview(www)
www.leftAnchor.constraint(equalTo: scroll.leftAnchor).isActive = true
www.rightAnchor.constraint(equalTo: scroll.rightAnchor).isActive = true
www.topAnchor.constraint(equalTo: scroll.topAnchor).isActive = true
www.bottomAnchor.constraint(equalTo: scroll.bottomAnchor).isActive = true
www.heightAnchor.constraint(equalToConstant:200).isActive = true
www.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier:2.0).isActive = true
scroll.addSubview(www)
}
}

Create a horizontal UIScrollView in swift programmatically

I would like to know how to create a horizontal scrollview in swift programmatically without using storyboards, with a page controller.
import UIKit
class SecondViewController: UIViewController, UIScrollViewDelegate {
var scrollview = UIScrollView()
var imageview = UIImageView()
var view1 = UIView()
override func viewDidLoad() {
super.viewDidLoad()
imageview.frame = CGRect(x: 0, y: 0, width: view.frame.size.width , height: 1000)
imageview.image = UIImage(named: "image")
scrollview.delegate = self
scrollview.contentSize = CGSize(width: imageview.frame.width, height: imageview.frame.height)
scrollview.backgroundColor = UIColor.red
imageview.backgroundColor = UIColor.green
self.scrollview.addSubview(imageview)
view.addSubview(scrollview)
scrollview.translatesAutoresizingMaskIntoConstraints = false
scrollview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
scrollview.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollview.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollview.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Do any additional setup after loading the view.
}
Try This
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
let textData: String = "Just to add onto the already great answers, you might want to add multiple labels in your project so doing all of this (setting size, style etc) will be a pain. To solve this, you can create a separate UILabel class."
override func viewDidLoad() {
super.viewDidLoad()
let textLable = UILabel(frame: CGRect(x: 0, y: 0, width: 1000, height: 200))
textLable.text = textData
self.scrollView = UIScrollView()
self.scrollView.delegate = self
self.scrollView.contentSize = CGSize(width: textLable.frame.width, height: textLable.frame.height)
scrollView.addSubview(textLable)
view.addSubview(scrollView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}