I have got this code:
var scrollView: UIScrollView!
scrollView = UIScrollView(frame: self.view.bounds)
scrollView.backgroundColor = .white
scrollView.contentSize = CGSize(width:self.view.frame.width,height:self.view.frame.height+1000)
scrollView.autoresizingMask = .flexibleHeight
view.addSubview(scrollView)
scrollView.addSubview(profileInfWrapper)
scrollView.addSubview(PostsTab)
scrollView.addSubview(FollowersTab)
scrollView.addSubview(FollowingTab)
scrollView.addSubview(AboutTab)
scrollView.addSubview(collectionView)
But unfortunately this code is adding a scroller. But when it is scrolling, it doesn't change anything. All subvies of scrollView have constraints.
I think you need to enable scrolling.
scrollView.isScrollEnabled = true
Related
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
}
}
I need to create a paging ScrollView which shows a sequence of images.
I created a ScrollView in the main view of the Storyboard and set this constraints (to center the ScrollView in the view):
Constraint
Then I activated paging and disabled the "Content layout guides" option.
Next, in the view class I set up the UIScrollViewDelegate delegate and I wrote the following code to show 3 images (they are 3 colored squares):
class ViewController: UIViewController, UIScrollViewDelegate {
// Outlet
#IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollview.delegate = self;
let infoArray = ["01", "02", "03"];
for i in 0..<infoArray.count {
let imageView = UIImageView();
imageView.contentMode = .scaleToFill;
imageView.image = UIImage(named: infoArray[i]);
let xPos = CGFloat(i) * scrollview.bounds.size.width;
imageView.frame = CGRect(x: xPos, y: 0, width: scrollview.bounds.size.width, height: scrollview.bounds.size.height);
imageView.layer.borderWidth = 1;
scrollview.contentSize.width = scrollview.frame.size.width * CGFloat(i+1);
scrollview.contentSize.height = scrollview.frame.size.height;
scrollview.addSubview(imageView);
}
scrollview.layer.borderWidth = 1;
}
}
I have set that the images must have the same width and height as the scrollview. But these are larger in the simulator (and in my iPhone 11) and therefore the display is incorrect. I show you the sequence of the 3 squares:
Page 1
Page 2
Page 3
Page 4
I can't understand where I'm wrong. Why don't the 3 images take the size of the scrollview?
Why are there 4 pages?
Thanks for your help
Okay, here is how you do it:
Your scrollview is created in storyboard and its layout is set. Make sure content layout guides is unchecked in the size inspector and paging is checked in the attribute inspector.
Add a stackview as a subview to your scrollview (this will act as the content view). Pin your stackView to all 4 edges of the scrollView.
Set Height and Width Equal to the scrollView height and width. Set the Width priority to 250. (that indicates that the scrollview will scroll horizontally)
Set the stackView to horizontal axis, fill alignment and fillEqually distribution.
Now, go back to viewDidLoad and add the following code below. ScrollViewContentView is the stackView that acts as a contentView for the scrollView. Note that since stackView is set to fillEqually, you only need set one of the image's width constraint.
scrollViewContentView.addArrangedSubview(image1)
scrollViewContentView.addArrangedSubview(image2)
scrollViewContentView.addArrangedSubview(image3)
image1.translatesAutoresizingMaskIntoConstraints = false
image2.translatesAutoresizingMaskIntoConstraints = false
image3.translatesAutoresizingMaskIntoConstraints = false
image1.backgroundColor = .blue
image2.backgroundColor = .yellow
image3.backgroundColor = .red
image1.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
I think you need to state the frame of the scrollview before you declare the imageViews x positions or widths.
class ViewController: UIViewController, UIScrollViewDelegate {
// Outlet
#IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollview.frame = view.frame // declared here
scrollview.delegate = self;
let infoArray = ["01", "02", "03"];
for i in 0..<infoArray.count {
let imageView = UIImageView();
imageView.contentMode = .scaleToFill;
imageView.image = UIImage(named: infoArray[i]);
let xPos = CGFloat(i) * scrollview.bounds.size.width;
imageView.frame = CGRect(x: xPos, y: 0, width: scrollview.bounds.size.width, height: scrollview.bounds.size.height);
imageView.layer.borderWidth = 1;
scrollview.contentSize.width = scrollview.frame.size.width * CGFloat(i+1);
scrollview.contentSize.height = scrollview.frame.size.height;
scrollview.addSubview(imageView);
}
scrollview.layer.borderWidth = 1;
}
}
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
After days of trying, and searching through countless SO/google/YouTube pages, I unclear how to accomplish this: I'm trying to place a single tall, narrow image inside a UIScrollView that only takes up a section of the screen, only scrolls vertically, and is preferably only coded programmatically, no Interface Builder at all.
I've managed to create the scrollView, set the backgroundColor to blue so I can see it and managed to use constraint anchors to pin it exactly where I need it to be. I then added the top and bottom labels as every video tutorial was telling me to, but I've since deleted these as they didn't seem necessary once I added the image.
The problems start as soon as I try to add the image. I've added an example image below as it's a tall, narrow image.
https://imgur.com/7qI1IaT
If you run the code with the image, you'll see:
The image scrolls horizontally as well as vertically. I'd have thought content.didOffset.x < 0 would work, but apparently not. There's probably a simple method to fix this but I'm yet to find it.
If the height of the image is less than the height of the scrollView, i want the image to stretch to fit the scrollView. I used both .scaleAspectFit and .scaleAspectFill and neither of these seemed to change anything.
The width of the image (or at least, the image I'm using, not the example image) is larger than the section of scrollView I have, and it goes off the screen. Again, I'm sure there's an easy fix to this, but I don't know.
Here is my code, but it's probably all wrong.
import UIKit
class ViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.frame.size.height = 3000
view.backgroundColor = UIColor.blue
return view
}()
let imageView: UIImageView = {
let image = UIImageView(image: imageLiteral)
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
func setupLayout() {
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 100).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.addSubview(imageView)
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
imageView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
imageView.frame.size.height = scrollView.frame.size.height
imageView.frame.size.width = scrollView.frame.size.width
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
setupLayout()
}
}
I don't know if I'm doing the right thing by adding the image as a subview of scrollView. I couldn't get the image to scroll at all until I changed it from the subview of view to scrollView. The labels in the tutorials I've seen were added that way, and it made more sense to me to add it into the scrollView than the main screen view, but again, this could be wrong.
I'm really not sure if it's the constraints, the contentSize or what, but it’s pretty clear I don't know what I’m doing, and I don't want to just wing it, so if anyone knows of any YouTube videos or websites that can help me out, I’d really appreciate it.
Again, apologies. I feel like this is a really simple fix, but I just don't have it.
There are a number of ways of accomplishing this, but I’d be inclined to set the zoomScale of the scroll view appropriate for this image view width, e.g.
// we want to make sure we adjust scale as views are laid out
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if imageView.intrinsicContentSize.width != 0 {
let scale = scrollView.bounds.width / imageView.intrinsicContentSize.width
scrollView.maximumZoomScale = scale
scrollView.minimumZoomScale = scale
scrollView.zoomScale = scale
}
}
To do that, you’ll have to set the delegate of the UIScrollView:
scrollView.delegate = self // we need to specify delegate so we can implement `viewForZooming(in:)`
And implement viewForZooming(in:):
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
So pulling that all together:
class ViewController: UIViewController {
let sampleImage: UIImage = ...
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
// view.frame.size.height = 3000 // not needed as we're using constraints
scrollView.backgroundColor = .blue
return scrollView
}()
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
// imageView.contentMode = .scaleAspectFit // not needed as we're going to let the intrinsic size dictate the size of the image view and therefore no scaling is happening
imageView.clipsToBounds = true
return imageView
}()
func setupLayout() {
view.addSubview(scrollView)
scrollView.addSubview(imageView)
imageView.image = sampleImage
scrollView.delegate = self // we need to specify delegate so we can implement `viewForZooming(in:)`
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 100),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
imageView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
imageView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
])
// these are not needed because we're using constraints
//
// imageView.frame.size.height = scrollView.frame.size.height
// imageView.frame.size.width = scrollView.frame.size.width
}
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
}
// we want to make sure we adjust scale as views are laid out
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if imageView.intrinsicContentSize.width != 0 {
let scale = scrollView.bounds.width / imageView.intrinsicContentSize.width
scrollView.maximumZoomScale = scale
scrollView.minimumZoomScale = scale
scrollView.zoomScale = scale
}
}
}
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
You need to constraint the image width to the scrollview width. However you cannot do it directly, because the image is a subview of the scrollview and direct constraint would refer to width of the content of the scrollview not width. I have solved it by adding a layout guide that is constrained to the width of the scrollview "from the outside".
Also when you add constraint for the width you are left with intrinsic constraint for the height and that would change aspect ratio of the image. You need to add a constraint for the original aspect ratio.
Here is my code:
class ViewController: UIViewController {
let scrollView = UIScrollView()
let imageView = UIImageView(image: UIImage(named: "tallimage"))
let widthGuide = UILayoutGuide()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupLayout()
}
func setupViews() {
scrollView.backgroundColor = UIColor.blue
view.addSubview(scrollView)
scrollView.addSubview(imageView)
view.addLayoutGuide(widthGuide)
}
func setupLayout() {
let ratio: CGFloat = (imageView.image?.size.height ?? 1) / (imageView.image?.size.width ?? 1)
imageView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 100),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
imageView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
imageView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
// Make the image the same width as the scrollview.
widthGuide.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
imageView.widthAnchor.constraint(equalTo: widthGuide.widthAnchor),
// Keep the height/width ratio of the image so it is not deformed.
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: ratio),
])
}
}
I have also changed style of the code. Feel free to use your original style.
However, I like to
When I call a method, it is defined below the line where it is used (in order).
Use NSLayoutConstraint.activate() when activating more constraints.
Use simple instance variables (let constants) and configure them later.
I have a Swift code
background_img_view.addSubview(background_image)
background_img_view.addSubview(usn)
profile_view.addSubview(PostsDiv)
profile_view.addSubview(PostDiv)
var scrollView: UIScrollView!
scrollView = UIScrollView(frame: view.bounds)
scrollView.contentSize = CGSize(width: self.view.frame.size.width, height: 1500)
scrollView.autoresizingMask = UIViewAutoresizing.flexibleHeight
scrollView.isScrollEnabled = true
scrollView.addSubview(profile_view)
view.addSubview(background_img_view)
view.addSubview(scrollView)
I tested this on my phone and scroll bar is showing, but when i scroll nothing changes, also i can't click on anything on the screen .
Full code http://pastebin.com/EXQeGMJ6