Home > OS >  (Swift 5) UIScrollView scrolls but none of the content scrolls (video included)
(Swift 5) UIScrollView scrolls but none of the content scrolls (video included)

Time:03-09

I'm trying to learn to build views without storyboard. I tried to build a scrollview. On that scrollview is a UISearchBar, a UIImageView with an image and a UILabel. It works but none of the content moves. The content is all just frozen in place like no matter how far I scroll the search bar will always be on top of the page. and the image on the bottom. I've attached a video to show what I mean. There's also a problem because none of the content is where I want it to be but that's another problem. I realize this is probably because I don't know enough about constraints and autolayout and building views without storyboards.

Here's the video

class HomePageViewController: UIViewController {

var searchedText: String = ""
let label = UILabel()

let searchBar: UISearchBar = {
    let searchBar = UISearchBar()
    searchBar.placeholder = "Where are you going?"
    searchBar.translatesAutoresizingMaskIntoConstraints = false
    searchBar.barTintColor = .systemCyan
    searchBar.searchTextField.backgroundColor = .white
    searchBar.layer.cornerRadius = 5
    return searchBar
}()

let homeImage: UIImageView = {
    let homeImage = UIImageView()
    homeImage.translatesAutoresizingMaskIntoConstraints = false
    homeImage.clipsToBounds = true
    return homeImage
}()

let scrollView: UIScrollView = {
    let scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.backgroundColor = .systemMint
    scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
    return scrollView
}()


override func viewDidLoad() {
    super.viewDidLoad()
    
    view.backgroundColor = .systemPink
 //        setupLayout()
 // tried this here doesn't do anything for me
}

func setupLayout() {
    view.addSubview(scrollView)
    self.scrollView.addSubview(searchBar)
    
    homeImage.image = UIImage(named: "Treehouse")
    self.scrollView.addSubview(homeImage)
    
    label.text = "Inspiration for your next trip..."
    self.scrollView.addSubview(label)
    // not sure where this label is being added I want it to be underneath the image but it isn't t
    
    let safeG = view.safeAreaLayoutGuide
    let viewFrame = view.bounds
    
    NSLayoutConstraint.activate([
        
        scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: -10),
        scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
        scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        
        searchBar.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 50.0),
        searchBar.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.9),
        searchBar.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
        
        homeImage.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 150),
        homeImage.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 1.1),
        homeImage.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
        homeImage.heightAnchor.constraint(equalToConstant: viewFrame.height/2),
        
        label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100)
    ])
 // was doing all this in viewDidLayoutSubviews but not sure if this is better place for it
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    setupLayout()
   // tried this in viewDidLoad() and it didn't solve it. 
}

}

any help would be appreciated

CodePudding user response:

First, when constraining subviews in a UIScrollView, you should constrain them to the scroll view's Content Layout Guide. You're constraining them to the view's safe area layout guide, so they're never going to go anywhere.

Second, it's difficult to center subviews in a scroll view, because the scroll view can scroll both horizontally and vertically. So it doesn't really have a "center."

You can either put subviews in a stack view, or, quite common, use a UIView as a "content" view to hold the subviews. If you constrain that content view's Width to the scroll view's Frame Layout Guide width, you can then horizontally center the subviews.

Third, it can be very helpful to comment your constraints, so you know exactly what you expect them to do.

Here's a modified version of your posted code:

class HomePageViewController: UIViewController {
    
    var searchedText: String = ""
    
    let label: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    let searchBar: UISearchBar = {
        let searchBar = UISearchBar()
        searchBar.placeholder = "Where are you going?"
        searchBar.translatesAutoresizingMaskIntoConstraints = false
        searchBar.barTintColor = .systemCyan
        searchBar.searchTextField.backgroundColor = .white
        searchBar.layer.cornerRadius = 5
        return searchBar
    }()
    
    let homeImage: UIImageView = {
        let homeImage = UIImageView()
        homeImage.translatesAutoresizingMaskIntoConstraints = false
        homeImage.clipsToBounds = true
        return homeImage
    }()
    
    let scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.backgroundColor = .systemMint
        // don't do this
        //scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
        return scrollView
    }()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemPink

        setupLayout()
    }
    
    func setupLayout() {

        view.addSubview(scrollView)
        
        //homeImage.image = UIImage(named: "Treehouse")
        homeImage.image = UIImage(named: "natureBKG")
        
        label.text = "Inspiration for your next trip..."
        
        // let's use a UIView to hold the "scroll content"
        let contentView = UIView()
        contentView.translatesAutoresizingMaskIntoConstraints = false

        // give it a green background so we can see it
        contentView.backgroundColor = .green
        
        contentView.addSubview(searchBar)
        contentView.addSubview(homeImage)
        contentView.addSubview(label)

        scrollView.addSubview(contentView)
        
        let safeG = view.safeAreaLayoutGuide
        
        let svContentG = scrollView.contentLayoutGuide
        let svFrameG = scrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([

            // constrain scrollView to all 4 sides of view
            //  (generally, constrain to safe-area, but this is what you had)
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
            scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            // constrain contentView to all 4 sides of scroll view's Content Layout Guide
            contentView.topAnchor.constraint(equalTo: svContentG.topAnchor, constant: 0.0),
            contentView.leadingAnchor.constraint(equalTo: svContentG.leadingAnchor, constant: 0.0),
            contentView.trailingAnchor.constraint(equalTo: svContentG.trailingAnchor, constant: 0.0),
            contentView.bottomAnchor.constraint(equalTo: svContentG.bottomAnchor, constant: 0.0),

            // constrain contentView Width equal to scroll view's Frame Layout Guide Width
            contentView.widthAnchor.constraint(equalTo: svFrameG.widthAnchor),

            // constrain searchBar Top to contentView Top   50
            searchBar.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 50.0),
            
            // constrain searchBar Width to 90% of contentView Width
            searchBar.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.9),
            
            // constrain searchBar centerX to contentView centerX
            searchBar.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            
            // constrain homeImage Top to searchBar Bottom   40
            homeImage.topAnchor.constraint(equalTo: searchBar.bottomAnchor, constant: 40.0),

            // constrain homeImage Width equal to contentView Width
            homeImage.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0),
            
            // constrain homeImage centerX to contentView centerX
            homeImage.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            
            // constrain homeImage Height to 1/2 of scroll view frame Height
            homeImage.heightAnchor.constraint(equalTo: svFrameG.heightAnchor, multiplier: 0.5),

            // you probably won't get vertical scrolling yet, so increase the vertical space
            //  between the homeImage and the label by changing the constant
            //  from 100 to maybe 400
            
            // constrain label Top to homeImage Bottom   100
            label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100.0),

            // constrain label centerX to contentView centerX
            label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),

            // constrain label Bottom to contentView Bottom - 20
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0),

        ])
    
    }
    
}
  • Related