-1

I have been trying to scroll the view for a whole day yesterday and I am not able to figure out why it won't scroll. I am not sure what I am doing wrong !!

I have looked at the solutions on stackoverflow:

UIScrollView Scrollable Content Size Ambiguity

How to append a character to a string in Swift?

Right anchor of UIScrollView does not apply

But still, the view doesn't scroll and the scrollview height should be equal to the conrainerView height. But in my case, it stays fixed to the height of the view.

Here is the code repo: https://bitbucket.org/siddharth_shekar/ios_colttestproject/src/master/

Kindly go through and any help would be appreciated. Thanks!!

Here is the code Snippet as well, If you want to go through the constraints and see if there is anything I have added which is not letting the scroll view do its thing !!

I have made changes to the view just one looong text and removed other images, labels, etc to produce the minimal reproducable code.

And I looked at this persons project as well. Their view scrolls!! https://useyourloaf.com/blog/easier-scrolling-with-layout-guides/

I am just not sure what I am doing differently!!!!

Here is my code for the contentView. It is literally just a textlabel

import Foundation
import UIKit


class RecipeUIView: UIView{

    private var recipeTitle: UILabel! = {
        
        let label = UILabel()
        label.numberOfLines = 0
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textColor = .gray
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        label.adjustsFontForContentSizeCategory = true
        
        return label
    }()
    
    
    func setupView(currentRecipe: Receipe?){
        
        recipeTitle.text = currentRecipe?.dynamicTitle

        addSubview(recipeTitle)

        let margin = readableContentGuide
        
        // Constraints
        
        recipeTitle.topAnchor.constraint(equalTo: margin.topAnchor, constant: 4).isActive = true
        recipeTitle.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20).isActive = true
        recipeTitle.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20).isActive = true
            
    }
    
}

And here is the viewController

import Foundation
import UIKit


class RecipeViewController: UIViewController {
    
    
    var selectedRecipe: Receipe?
    
    let recipeView:  RecipeUIView = {
        
        let view = RecipeUIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()

    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        recipeView.setupView(currentRecipe: selectedRecipe)
        recipeView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)

        
        view.addSubview(scrollView)
        scrollView.addSubview(recipeView)
        
        let frameGuide = scrollView.frameLayoutGuide
        let contentGuide = scrollView.contentLayoutGuide
        
        
        // Scroll view layout guides (iOS 11)
        NSLayoutConstraint.activate([
            frameGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            frameGuide.topAnchor.constraint(equalTo: view.topAnchor),
            frameGuide.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            frameGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            contentGuide.leadingAnchor.constraint(equalTo: recipeView.leadingAnchor),
            contentGuide.topAnchor.constraint(equalTo: recipeView.topAnchor),
            contentGuide.trailingAnchor.constraint(equalTo: recipeView.trailingAnchor),
            contentGuide.bottomAnchor.constraint(equalTo: recipeView.bottomAnchor),

            contentGuide.widthAnchor.constraint(equalTo: frameGuide.widthAnchor),

            ])

    }

}

And I am still not able to scroll the view. Here is a screenshot of my project output. Still no scroll guide lines on the right!!

enter image description here

UPDATE:: Now the text scrolls, but when I add a UITableView in the UIView the scrolling works but the tableView is not seen in the UiView.

Is it due to the constraints again???

here is the code for the same::

class RecipeUIView: UIView, UITableViewDelegate, UITableViewDataSource{

    
    var currentRecipe: Receipe?
    
    private let tableView: UITableView = {
        let tableView = UITableView()

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
        
        tableView.backgroundColor = .green

        
        return tableView
    }()
    
    
    private var recipeTitle: UILabel! = {
        
        let label = UILabel()
        label.numberOfLines = 0
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textColor = .gray
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        label.adjustsFontForContentSizeCategory = true
        
        return label
    }()
    

    override init(frame: CGRect) {
        super.init(frame: frame)
            }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        
        print("++++ IngrediantsTableViewCell tableview count: \(currentRecipe?.ingredients.count ?? 0)")
        
        return currentRecipe?.ingredients.count ?? 0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
        print("++++ IngrediantsTableViewCell tableview cellForRow ")

        
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
            cell.textLabel!.text = "\(currentRecipe?.ingredients[indexPath.row].ingredient ?? "")"
            return cell

    }
    
    

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

        //return UITableView.automaticDimension
        
        return 30

    }
    
    
    func setupView(currentRecipe: Receipe?){
        
        let margin = readableContentGuide
        
        
        self.currentRecipe = currentRecipe

        recipeTitle.text = currentRecipe?.dynamicTitle


        addSubview(recipeTitle)


        // Constraints

        recipeTitle.topAnchor.constraint(equalTo: margin.topAnchor, constant: 4).isActive = true
        recipeTitle.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20).isActive = true
        recipeTitle.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20).isActive = true

        addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: recipeTitle.bottomAnchor, constant: 10).isActive = true
        tableView.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20).isActive = true
        tableView.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20).isActive = true

        tableView.bottomAnchor.constraint(equalTo: margin.bottomAnchor, constant: -20).isActive = true

        tableView.reloadData()
                
    }
    
    
}
Siddharth Shekar
  • 438
  • 7
  • 20

1 Answers1

1

You are missing a constraint...

In your RecipeUIView class, you have this:

func setupView(currentRecipe: Receipe?){
    
    recipeTitle.text = currentRecipe?.dynamicTitle

    addSubview(recipeTitle)

    let margin = readableContentGuide
    
    // Constraints
    
    recipeTitle.topAnchor.constraint(equalTo: margin.topAnchor, constant: 4).isActive = true
    recipeTitle.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20).isActive = true
    recipeTitle.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20).isActive = true

}

So, you have no constraint controlling the view's Height.

Add this line:

    recipeTitle.bottomAnchor.constraint(equalTo: margin.bottomAnchor, constant: -20).isActive = true

And you'll get vertical scrolling.

Two side notes...

First, in ``RecipeViewController`, change your constraints like this:

NSLayoutConstraint.activate([
            
    scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
    scrollView.topAnchor.constraint(equalTo: view.topAnchor),
    scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
    recipeView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
    recipeView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
    recipeView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
    recipeView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor),
            
    recipeView.widthAnchor.constraint(equalTo: frameGuide.widthAnchor),
            
])

There's no real functional difference, but it is more logical and more readable to think in terms of:

  • I'm constraining the scrollView to the view
  • I'm constraining the recipeView to the scroll view's .contentLayoutGuide (which determines the "scrollable" size)
  • I'm constraining the recipeView width the the scroll view's .frameLayoutGuide

Second, giving views contrasting background colors can be very helpful when trying to debug layouts.

For example, if I set background colors like this:

  • recipeTitle label : cyan
  • recipeView : yellow
  • scrollView : orange

It looks like this when running (with your original constraints):

enter image description here

Since the cyan label is a subview of the yellow view, it is obvious that the yellow view height is not correct.

After add the missing bottom constraint, it looks like this:

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks that worked. Probably I was so focused on the viewController that I didnt see the RecipeView. Now I tried adding a tableView after the recipeTitle in the RecipeView. I tried constraining it the same way I constrained the recipeTitle but now cellForRowAt () doesnt get called. Any Idea as to why? The numberOfRowsInSection() does get called. Thanks!! – Siddharth Shekar Aug 15 '22 at 21:10
  • @SiddharthShekar - I'd have to see your code for that... If you're *"adding a tableView after the recipeTitle in the RecipeView"* I assume you got rid of the scrollView? – DonMag Aug 15 '22 at 21:29
  • Sry I just pushed the code. The tableView is inside the recipeView. The scrollview is still present in the viewController and the RecipeView is added in the ScrollView. Thanks verymuch for looking into the code. – Siddharth Shekar Aug 15 '22 at 21:38
  • 1
    @SiddharthShekar - a `UIScrollView` has no intrinsic height, and you haven't given it a height. Add this line: `tableView.heightAnchor.constraint(equalToConstant: 300.0).isActive = true` in `func setupView(currentRecipe: Receipe?)` in `RecipeUIView` (for example). – DonMag Aug 15 '22 at 22:09
  • Thanks!! That certainly works, but it only works on the first recipe. On the other recipes the last couple of cells get clipped because of the height constraint :| – Siddharth Shekar Aug 15 '22 at 23:58
  • 1
    @SiddharthShekar - OK - it can be a good idea to explain what your ultimate goal is from the start... I assumed you wanted the table view to scroll independently once you had scrolled it up into view. If that's not the case, then I would suggest either **A)** get rid of the scroll view, and instead use a table view with the first row being your description cell and the remaining rows being ingredient cells, or **B)** stick with what you have, but use labels in a stack view for your ingredients instead of a table view. – DonMag Aug 16 '22 at 00:38
  • Thanks for the options. So the real application RecipeView has not only description, but description text, details text, an image, stackview with cooking, prep, serve count, another text after that and then an array of texts which are part of the ingrediants. I was thinking to add the ingrediants as a table view as I wouldnt have to create an textarray but then if you think it is more optimum it being a text array then I think I should leave it at that. Thanks so much of the help I've really learned a lot with your guidance. Thanks for your patience and guidance. – Siddharth Shekar Aug 16 '22 at 05:45