I can't add subViews in UIScrollView - swift

I'm trying add a subview to UIScrollView. I add scrollView, subView and set constraints bellow :
class ViewController: UIViewController {
let scrollView : UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .yellow
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.alwaysBounceVertical = true
return scrollView
}()
let catImageView : UIImageView = {
let img = UIImageView()
img.translatesAutoresizingMaskIntoConstraints = false
img.backgroundColor = .white
return img
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
view.addSubview(scrollView)
scrollView.frame = self.view.bounds
scrollView.contentSize = CGSize(width: self.view.frame.width, height: 1000)
scrollView.addSubview(catImageView)
catImageView.centerXAnchor.constraint(equalTo: self.scrollView.centerXAnchor).isActive = true
catImageView.centerYAnchor.constraint(equalTo: self.scrollView.centerYAnchor).isActive = true
catImageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
catImageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
I builded and scrollView and subiew are disappear. I don't know why...
Then I trying add subview another way like this :
view.addSubview(catImageView)
catImageView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
catImageView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
catImageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
catImageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
It's still the same. Please explained to me why. Thank a lot

You should only set the translatesAutoresizingMaskIntoConstraints to false in case you are adding the constraints to the view. You've set the property on the scrollView to false, but used the frame, and not constraints. Remove the line:
scrollView.translatesAutoresizingMaskIntoConstraints = false // <- remove
and it should work.
Hope this helps! Good luck!

Related

stack programmatically, giving space for bottom

i am trying to place the image below the text i add
class SolicitudViewController: BaseViewController {
lazy var imagePrincipal : UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "diseƱo")
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
//imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
//imageView.bottomAnchor.constraint(equalToConstant: 100).isActive = true
return imageView
}()
lazy var stackView : UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fill
stack.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(imagePrincipal)
//stack.addArrangedSubview(lblsubTitulo)
//stack.addArrangedSubview(lineView)
stack.addArrangedSubview(imageEvaluando2)
stack.addArrangedSubview(imageEvaluando3)
return stack
}()
lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
setSubtitle(subtitle: "test View")
}
I have tried to use this: stack.setCustomSpacing(30, after: imagePrincipal) but it positions the image on top, I want the image to be below the text

How to use automatically/dynamically set scrollview to fit the content view

Surprisingly, this is harder than I thought. I followed this tutorial which seems rather straightforward but I am programmatically creating my view instead of using storyboard. Just to be clear, the content I add to the content view is static i.e. it's not growing or increasing.
Here are the definitions of scroll view and content view:
lazy var contentView : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView(frame: .zero)
scrollView.backgroundColor = .white
scrollView.frame = self.view.bounds
scrollView.bounces = true
scrollView.autoresizingMask = .flexibleHeight
scrollView.contentSize = CGSize(width: self.view.frame.width, height: contentView.frame.height)
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
In view did load add the scroll view and set its constraints in the view controller:
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
Then I add content view and the constraints:
scrollView.addSubview(contentView)
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
let constraint = contentView.heightAnchor.constraint(equalTo: view.heightAnchor)
constraint.priority = UILayoutPriority(250)
constraint.isActive = true
The problem is that I cannot scroll all the way to the bottom.
Initial points to note
Delete
scrollView.frame = self.view.bounds
It is pointless to give scrollView a frame, as you are going to give it a frame later through the use of constraints.
Delete
scrollView.autoresizingMask = .flexibleHeight
You are using constraints, not the autoresizing mask, to give the scroll view its frame and later resizing behavior.
Delete
scrollView.contentSize = CGSize(width: self.view.frame.width, height: contentView.frame.height)
Once the scroll view is under the influence of constraints, you must use constraints, not contentSize, to give it a content size that determines scroll behavior.
Adding the content view
With those preliminaries out of the way, let's talk about how you add the content view to the scroll view:
scrollView.addSubview(contentView)
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
That is not exactly wrong, but it is very outmoded. You should pin the content view to the scroll view's content layout guide; that is what it is for. So, wherever you have equalTo: scrollView, change it to equalTo: scrollView.contentLayoutGuide.
Scrolling
Okay! Now everything is assembled and we are ready to talk about scrolling. What makes the scroll view scrollable, in this configuration, is that the content view is bigger than the scroll view itself. Well, so far, that's not true; in fact, the content view has no size at all. So we must proceed to give it some size.
Your attempt to do that is rather feeble. Let's extract the key lines where you give the content view height and width constraints:
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
contentView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
(Delete the other two lines, as they are not really doing anything useful now.) So now is the content view any bigger than the scroll view? Well, maybe, but if so, only by a tiny bit, because the content view is merely the size of the main view, and the scroll view is either that same size or a tiny bit smaller.
Since we are just demonstrating, it would be better to make the content view a lot bigger than the scroll view, so we can do some major scrolling. Change the second line to this:
contentView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier:2 ).isActive = true
Yeah, baby! Now we can really scroll.
Making the content more visible
Still, it's a little hard to see what's happening (everything is white on white), so I suggest you fill the content view with some color that will permit us to see what's going on. Declare a self-drawing view as follows:
class MyView : UIView {
override class var layerClass : AnyClass { CAGradientLayer.self }
override func willMove(toSuperview newSuperview: UIView?) {
let lay = self.layer as! CAGradientLayer
lay.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
}
}
Now change
let view = UIView()
to
let view = MyView()
Now it is very obvious when you scroll to the bottom; the real green is visible at the bottom.
Summary
Here is the complete code of the corrected example:
class MyView : UIView {
override class var layerClass : AnyClass { CAGradientLayer.self }
override func willMove(toSuperview newSuperview: UIView?) {
let lay = self.layer as! CAGradientLayer
lay.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
}
}
class ViewController: UIViewController {
lazy var contentView : UIView = {
let view = MyView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView(frame: .zero)
scrollView.backgroundColor = .white
scrollView.bounces = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.addSubview(contentView)
contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
contentView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier:2 ).isActive = true
}
}

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

Swift add GestureRecognizer to View on top of UIStackView

I'm trying to add a GestureRecognizer to a UIView which is an arranged subview of a UIStackView. I tried already this this or this. Also I tried to enable all underlying views for userInteraction, but it didn't work.
When I add the gesture recognizer to the imageView which is the parent view of the stackView, then the tap works. Strangely it also get's triggered when I tap on the "bannerView" which is a subview of the stackView and therefore lies on top of the imageView.
What's the proper way to get the tap gesture recognize on top of a stackViews's arrangedSubview?
Here my code:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
var imageView: UIImageView!
var bannerStackView: UIStackView!
var bannerView: UIView!
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
setupImageView()
setupBannerStackView()
setupConstraints()
}
func setupImageView() {
let image = UIImage(named: "Apple.jpeg")
imageView = UIImageView(image: image)
imageView.backgroundColor = .red
view.addSubview(imageView)
}
func setupBannerStackView() {
bannerStackView = UIStackView()
bannerStackView.axis = .vertical
bannerStackView.alignment = .leading
bannerStackView.distribution = .equalCentering
bannerStackView.isUserInteractionEnabled = true
bannerView = UIView()
bannerView.backgroundColor = .blue
bannerStackView.addArrangedSubview(bannerView)
imageView.addSubview(bannerStackView)
/*
Also tried this but didn't work
*/
// let tapGesture = UITapGestureRecognizer(target: self, action:
#selector(onBannerTapped))
// bannerView.isUserInteractionEnabled = true
// bannerView.isUserInteractionEnabled = true
// bannerView.addGestureRecognizer(tapGesture)
for bannerView in bannerStackView.arrangedSubviews {
let tapGesture = UITapGestureRecognizer(target: self, action:
#selector(onBannerTapped))
bannerView.isUserInteractionEnabled = true
bannerView.addGestureRecognizer(tapGesture)
}
}
#objc func onBannerTapped() {
print("Banner view tapped!")
}
func setupConstraints() {
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
bannerStackView.translatesAutoresizingMaskIntoConstraints = false
bannerStackView.topAnchor.constraint(equalTo: imageView.topAnchor, constant: 10).isActive = true
bannerStackView.leadingAnchor.constraint(equalTo: imageView.leadingAnchor).isActive = true
bannerView.translatesAutoresizingMaskIntoConstraints = false
bannerView.heightAnchor.constraint(equalToConstant: 30).isActive = true
bannerView.widthAnchor.constraint(equalToConstant: 80).isActive = true
}
}
.isUserInteractionEnabled cascades to subviews. So, even if I set it to true on a subview, if the subview's superview (or its superview, and on up the hierarchy) has .isUserInteractionEnabled set to false, the subview will not get touch events.
So, the first thing to do is check .isUserInteractionEnabled on each view in the hierarchy.
In your func, remove this line:
bannerStackView.isUserInteractionEnabled = true
then add these lines after creating your tapGesture:
print("imageView isUserInteractionEnabled:", imageView.isUserInteractionEnabled)
print("bannerStackView isUserInteractionEnabled:", bannerStackView.isUserInteractionEnabled)
print("bannerView isUserInteractionEnabled:", bannerView.isUserInteractionEnabled)
So it looks like this:
func setupBannerStackView() {
bannerStackView = UIStackView()
bannerStackView.axis = .vertical
bannerStackView.alignment = .leading
bannerStackView.distribution = .equalCentering
// remove this line (comment-it out)
// bannerStackView.isUserInteractionEnabled = true
bannerView = UIView()
bannerView.backgroundColor = .blue
bannerStackView.addArrangedSubview(bannerView)
imageView.addSubview(bannerStackView)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onBannerTapped))
bannerView.addGestureRecognizer(tapGesture)
print("view isUserInteractionEnabled:", view.isUserInteractionEnabled)
print("imageView isUserInteractionEnabled:", imageView.isUserInteractionEnabled)
print("bannerStackView isUserInteractionEnabled:", bannerStackView.isUserInteractionEnabled)
print("bannerView isUserInteractionEnabled:", bannerView.isUserInteractionEnabled)
}
You should see this output:
view isUserInteractionEnabled: true
imageView isUserInteractionEnabled: false
bannerStackView isUserInteractionEnabled: true
bannerView isUserInteractionEnabled: true
and that tells you.... you simply need to add:
imageView.isUserInteractionEnabled = true

Can't add subview to bottom of scrollView

So, i'm trying to add a button to bottom of the scrollView but position of the button not as expected, the button's position at the top of the scrollView when i scroll to top, here's my code :
lazy var scrollView : UIScrollView = {
let scrollV = UIScrollView()
scrollV.translatesAutoresizingMaskIntoConstraints = false
scrollV.alwaysBounceVertical = true
scrollV.delegate = self
scrollV.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return scrollV
}()
in viewDidLoad :
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: self.topBar.bottomAnchor).isActive = true
scrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
scrollView.contentSize = CGSize(width: self.view.frame.width, height: 1000)
finally, I add the button to the scrollView :
scrollView.addSubview(myButton)
myButton.centerXAnchor.constraint(equalTo: self.scrollView.centerXAnchor).isActive = true
myButton.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor).isActive = true
myButton.widthAnchor.constraint(equalToConstant: 150).isActive = true
myButton.heightAnchor.constraint(equalToConstant: 50).isActive = true