In my storyboard I have a label and a UIScrollView subclass with the scrollview constraints placing it directly under the label:
In code I am adding a single view that will have the content:
let contentView = UIView(frame: CGRectMake(0, 0, 2000, 1000))
override func viewDidLoad() {
contentView.backgroundColor = UIColor.whiteColor()
tableView.addSubview(contentView)
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return contentView
}
When the scrollView shows, the top is not up against the label:
Why isn't the scrollview up against the label?
Add this to your view controller...
Self.automaticallyAdjustsScrollViewInsets = false
Related
I have a view controller with some elements in it.
class MyViewController: UIViewController {
// ......
}
This is what I want:
example of what I want
This is what my app looks like:
I want to make the two elements (an imageView and a button) to be vertically scrollable like the example.
What should I do? I have checked UIScrollView, but still don't know how to do it. Can you show me some code? Thanks!
There is an UI Component inside UIKit called UIScrollView. This Component can be used to achieve the Scrolling Behaviour you probably aim for.
Check out the official Documentation by Apple
UIScrollView | Apple Developer Documentation
Another great resource for getting started with UIScrollViews is this one.
UIScrollView Tutorial: Getting Started
One way to do this is programmatically create an UIScrollView in your UIViewController.
To control the scrollability you can set the ScrollView contentSize property. In the following sample we match contentSize.width with self.view.frame.size.width to prevent horizontal scroll.
So, to achieve your goal, you must add your components inside the scrollView using scrollView.addSubview
This is a simple sample of a UIScrollView with a red UIView inside:
import UIKit
class MyViewController: UIViewController, UIScrollViewDelegate {
lazy var scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.translatesAutoresizingMaskIntoConstraints = false
scroll.delegate = self
scroll.contentSize = CGSize(width: self.view.frame.size.width, height: 1000)
return scroll
}()
lazy var redView:UIView = {
let redview = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
redview.backgroundColor = .red
return redview
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(redView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let layout = view.safeAreaLayoutGuide
scrollView.centerXAnchor.constraint(equalTo: layout.centerXAnchor).isActive = true
scrollView.centerYAnchor.constraint(equalTo: layout.centerYAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: layout.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: layout.heightAnchor).isActive = true
}
}
I'm trying to follow the example described here for making a stretchy layout which includes a UIImageView and UIScrollView. https://github.com/TwoLivesLeft/StretchyLayout/tree/Step-6
The only difference is that I replace the UILabel used in the example with the view of a child UIViewController which itself contains a UICollectionView. This is how my layout looks - the blue items are the UICollectionViewCell.
This is my code:
import UIKit
import SnapKit
class HomeController: UIViewController, UIScrollViewDelegate {
private let scrollView = UIScrollView()
private let imageView = UIImageView()
private let contentContainer = UIView()
private let collectionViewController = CollectionViewController()
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentInsetAdjustmentBehavior = .never
scrollView.delegate = self
imageView.image = UIImage(named: "burger")
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
let imageContainer = UIView()
imageContainer.backgroundColor = .darkGray
contentContainer.backgroundColor = .clear
let textBacking = UIView()
textBacking.backgroundColor = #colorLiteral(red: 0.7450980544, green: 0.1235740449, blue: 0.2699040081, alpha: 1)
view.addSubview(scrollView)
scrollView.addSubview(imageContainer)
scrollView.addSubview(textBacking)
scrollView.addSubview(contentContainer)
scrollView.addSubview(imageView)
self.addChild(collectionViewController)
contentContainer.addSubview(collectionViewController.view)
collectionViewController.didMove(toParent: self)
scrollView.snp.makeConstraints {
make in
make.edges.equalTo(view)
}
imageContainer.snp.makeConstraints {
make in
make.top.equalTo(scrollView)
make.left.right.equalTo(view)
make.height.equalTo(imageContainer.snp.width).multipliedBy(0.7)
}
imageView.snp.makeConstraints {
make in
make.left.right.equalTo(imageContainer)
//** Note the priorities
make.top.equalTo(view).priority(.high)
//** We add a height constraint too
make.height.greaterThanOrEqualTo(imageContainer.snp.height).priority(.required)
//** And keep the bottom constraint
make.bottom.equalTo(imageContainer.snp.bottom)
}
contentContainer.snp.makeConstraints {
make in
make.top.equalTo(imageContainer.snp.bottom)
make.left.right.equalTo(view)
make.bottom.equalTo(scrollView)
}
textBacking.snp.makeConstraints {
make in
make.left.right.equalTo(view)
make.top.equalTo(contentContainer)
make.bottom.equalTo(view)
}
collectionViewController.view.snp.makeConstraints {
make in
make.left.right.equalTo(view)
make.top.equalTo(contentContainer)
make.bottom.equalTo(view)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.scrollIndicatorInsets = view.safeAreaInsets
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: view.safeAreaInsets.bottom, right: 0)
}
//MARK: - Scroll View Delegate
private var previousStatusBarHidden = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if previousStatusBarHidden != shouldHideStatusBar {
UIView.animate(withDuration: 0.2, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
previousStatusBarHidden = shouldHideStatusBar
}
}
//MARK: - Status Bar Appearance
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
override var prefersStatusBarHidden: Bool {
return shouldHideStatusBar
}
private var shouldHideStatusBar: Bool {
let frame = contentContainer.convert(contentContainer.bounds, to: nil)
return frame.minY < view.safeAreaInsets.top
}
}
Everything is the same as in this file: https://github.com/TwoLivesLeft/StretchyLayout/blob/Step-6/StretchyLayouts/StretchyViewController.swift with the exception of the innerText being replaced by my CollectionViewController.
As you can see, the UICollectionView is displayed properly - however I am unable to scroll up or down anymore. I'm not sure where my mistake is.
It looks like you are constraining the size of your collection view to fit within the bounds of the parent view containing the collection view's container view and the image view. As a result, the container scrollView has no contentSize to scroll over, and that's why you can't scroll. You need to ensure your collection view's content size is reflected in the parent scroll view's content size.
In the example you gave, this behavior was achieved by the length of the label requiring a height greater than the height between the image view and the rest of the view. In your case, the collection view container needs to behave as if it's larger than that area.
Edit: More precisely you need to pass the collectionView.contentSize up to your scrollView.contentSize. A scrollview's contentSize is settable, so you just need to increase the scrollView.contentSize by the collectionView.contentSize - collectionView.height (since your scrollView's current contentSize currently includes the collectionView's height). I'm not sure how you are adding your child view controller, but at the point you do that, I would increment your scrollView's contentSize accordingly. If your collectionView's size changes after that, though, you'll also need to ensure you delegate that change up to your scrollView. This could be accomplished by having a protocol such as:
protocol InnerCollectionViewHeightUpdated {
func collectionViewContentHeightChanged(newSize: CGSize)
}
and then making the controller containing the scrollView implement this protocol and update the scrollView contentSize accordingly. From your collectionView child controller, you would have a delegate property for this protocol (set this when creating the child view controller, setting the delegate as self, the controller containing the child VC and also the scrollView). Then whenever the collectionView height changes (if you add cells, for example) you can do delegate.collectionViewContentHeightChanged(... to ensure your scroll behavior will continue to function.
I am unable to set the width of the scrollView to the width of the screen. Could someone please guide where I am going wrong ?
Here is how I have enabled a scroll view
// SCROLL VIEW
var scrollView = UIScrollView()
var contentView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
screenHeight = screenSize.height
screenWidth = screenSize.width
// SCROLL VIEW
scrollView = UIScrollView(frame: view.bounds)
// Here I am adding all my labels and textview fields to content view
contentView.addSubview(problemDescriptionStackView)
....
// Adding content view to the scrollView
scrollView.addSubview(contentView)
// Pinning the contentView to the scroll view
pinView(contentView, to: scrollView)
self.view.addSubview(scrollView)
// Here I am Setting the constraints for all the items in the contentView
....
}
// The below functions are used to pin one view to another view
public func pinView(_ view: UIView, to scrollView: UIScrollView) {
view.translatesAutoresizingMaskIntoConstraints = false
view.pin(to: scrollView)
}
public func pin(to view: UIView) {
NSLayoutConstraint.activate([
leadingAnchor.constraint(equalTo: view.leadingAnchor),
trailingAnchor.constraint(equalTo: view.trailingAnchor),
topAnchor.constraint(equalTo: view.topAnchor),
bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
Use below methods for get exact size of screen accordingly iPhone.
Hope using this method, your problem solved. If you have doubt Please comment here.
//MARK: - Main Screen Width
func getScreenWidth() -> CGFloat{
return UIScreen.main.bounds.size.width
}
//MARK: - Main Screen Height
func getScreenHeight() -> CGFloat{
return UIScreen.main.bounds.height
}
viewDidLoad load your ui from storyboard or nib. And size depend on what you select in Interface Builder.
You can initialize ui in viewWillAppear and add a variable isInitialized .
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !isInitialized {
isInitialized = true
// do your stuff
}
}
Scroll.contentSize = CGSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
make one subview horizontally center (using autolayout constraint) to a scrollview in interface builder or by programmatically
Ok so there are a couple of ways you can fix this:
1.- in the storyboard go to your view and you're going to hold the left click in your scroll view and release it over the main view, then you'll click equal width
2.- in the view did load put
self.scrollViewName.frame.size.width = self.view.frame.width
You can set the contentInset on the scrollView, and it will adjust the content size of the scrolling area within the scrollView, here is a quick example
scrollView.contentInset UIEdgeInsets(top: 0, left: 0, bottom: 0, right: padding)
That should do the trick :)
Try following code, may be you are not able to change frame because of autoresizing, So try to write following code in viewDidAppear method
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Set the frame of scrollview
self.scrollViewName.frame = CGRectMake(self.scrollViewName.frame.origin.x,
self.scrollViewName.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
}
Pin your scrollView to view instead of setting its frame.
Add a constraint that sets the width of the contentView equal to the width of the scrollView:
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
As you are setting constraints from viewDidLoad, but view is not properly layout in this method. You should set constraint when lay out of views is done. So you should set constraints in :
override func viewDidLayoutSubviews() {
// set constraints here
}
And you can use a variable to check if constraints are already added.
So I've seen other SO questions but I can't really figure out mine
I know my UIScrollView code works because I tested it in a regular UIViewController so I don't think contentSize is the problem, but when I place the same code inside a UICollectionReusableView it doesn't scroll anymore
-Working in UIViewController
override func viewDidLoad() {
super.viewDidLoad()
let scrollingView = colorButtonsView(buttonSize: CGSize(width:100.0,height:50.0), buttonCount: 10)
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
scrollView.backgroundColor = UIColor.black
scrollView.contentSize = scrollingView.frame.size
scrollView.showsHorizontalScrollIndicator = true
self.view.addSubview(scrollView)
scrollView.addSubview(scrollingView)
}
-Not working in UICollectionReusableView
override init(frame: CGRect) {
super.init(frame: frame)
//Add category scroll view
let scrollingView = colorButtonsView(buttonSize: CGSize(width:100.0,height:50.0), buttonCount: 10)
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
scrollView.backgroundColor = UIColor.black
scrollView.contentSize = scrollingView.frame.size
scrollView.showsHorizontalScrollIndicator = true
self.addSubview(scrollView) //Only changes self.view to self
scrollView.addSubview(scrollingView)
}
Better late than never...I had the same problem, so maybe this is why;
I wanted to put a UIScrollView inside a UICollectionReusableView but by accident I had created my custom class as;
class CustomCellHeader: UICollectionViewCell {
}
instead of;
class CustomCellHeader: UICollectionReusableView {
}
Once I changed that, the UIScrollView within my header cell came to life!
I have a UICollectionViewController (embedded in a NavigationViewController), which scrolls a UICollectionView horizontally via paging through some sections:
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
collectionView?.backgroundColor = .white
collectionView?.register(FeedCell.self, forCellWithReuseIdentifier: cellId)
//collectionView?.contentInset = UIEdgeInsetsMake(MenuBar.height, 0, 0, 0)
//collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(MenuBar.height, 0, 0, 0)
collectionView?.isPagingEnabled = true
Each section or page contains another UICollectionView (inside the FeedCell) which scrolls vertically through some UICollectionViewCells.
Inside the UICollectionViewController, I set
navigationController?.hidesBarsOnSwipe = true
which was working as long as there was only one UICollectionView. But since the (Top)CollectionView is scrolling horizontally and is containing additional (Sub)CollectionView, that are scrolling vertically, this feature seems not to work any longer.
I would like the NavigationBar to hide when the (Sub)CollectionView is scrolling vertically. Is there any hack to achieve this?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let navigationBar = self.navigationController?.navigationBar {
let clampedYOffset = contentOffset.y <= 0 ? 0 : -contentOffset.y
navigationBar.transform = CGAffineTransform(translationX: 0, y: clampedYOffset)
self.additionalSafeAreaInsets.top = clampedYOffset
}
}
This is a solution that I came up with. Basically modify the transform of the NavigationBar to move it out the way when necessary. I also modify the additionalSafeAreaInset, as this will automatically shift all your content up to fill the space left by the navigation bar.
This function will be called as part of the UICollectionViewDelegate protocol.
This was suitable for my purposes - but if you want the navigation bar to appear when the user rapidly scrolls up (like in safari) you will have to add some additional logic.
Hope this helps!
You can try the code like this (Swift 3.0):
extension ViewController: UICollectionViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let isScrollingUp = scrollView.contentOffset.y - lastY > 0
lastY = scrollView.contentOffset.y
self.navigationController?.setNavigationBarHidden(isScrollingUp, animated: true)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
// show navigation bar ?
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
// show navigationBar ?
}
}