Search code examples
swiftuiscrollviewuikitresize

How to resize content view in UIScrollView programmatically?


Here is the issue: I need to add a view containing other subviews to a scroll view programmatically. In addition, I also need to make the frame of such a view to stick to the bounds of the main super view. The code below shows the approach I was trying to implement, but as you can see from the pictures below the 'contentView' is not updating its frame size when the screen is rotated. The initial code is taken from here for demonstration purposes. Any help would be greatly appreciated.

import UIKit

class TestViewController : UIViewController {
    
    var contentViewSize = CGSize()
    
    let contentView: UIView = {
        let view = UIView()
        view.backgroundColor = .magenta
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    let labelOne: UILabel = {
        let label = UILabel()
        label.text = "Scroll Top"
        label.backgroundColor = .red
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    let labelTwo: UILabel = {
        let label = UILabel()
        label.text = "Scroll Bottom"
        label.backgroundColor = .green
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    let scrollView: UIScrollView = {
        let v = UIScrollView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .cyan
        return v
    }()
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        contentViewSize = view.bounds.size
        labelTwo.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: (contentViewSize.width - labelTwo.frame.size.width - 16.0)).isActive = true
        
        labelTwo.topAnchor.constraint(equalTo: contentView.topAnchor, constant: (contentViewSize.height - labelTwo.frame.size.height - 16.0)).isActive = true
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        contentViewSize = view.bounds.size
        
        view.backgroundColor = .yellow
        
        self.view.addSubview(scrollView)
        
        scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
        scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
        
        scrollView.addSubview(contentView)
        
        contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16).isActive = true
        contentView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16).isActive = true
        contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 16).isActive = true
        contentView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 16).isActive = true
        
        contentView.addSubview(labelOne)
        
        labelOne.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16.0).isActive = true
        labelOne.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16.0).isActive = true
        
        contentView.addSubview(labelTwo)
        
        labelTwo.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: (contentViewSize.width - labelTwo.frame.size.width - 16.0)).isActive = true
        
        labelTwo.topAnchor.constraint(equalTo: contentView.topAnchor, constant: (contentViewSize.height - labelTwo.frame.size.height - 16.0)).isActive = true
        
        labelTwo.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -16.0).isActive = true
        labelTwo.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16.0).isActive = true
        
    }
    
}

portrait view

landscape view


Solution

  • You made some odd changes to the code from the answer you linked to. Also, that answer is a little out-of-date.

    Here's a better example. Assuming you want only vertical scrolling, this will:

    • add a Cyan scroll view, inset 8-pts on each side from the safe-area
    • add a Magenta "content view" to the scroll view, with 16-pts on each side constrained to the scroll view's contentLayoutGuide, with a width 32-pts less than the scroll view's frame (16-pts on each side)
    • add a label at top-left of the content view
    • add a label at bottom-right of the content view
    • constrain the bottom label 1500-pts below the top label (so it will scroll vertically)

    Code:

    class ScrollTestViewController : UIViewController {
        
        let contentView: UIView = {
            let view = UIView()
            view.backgroundColor = .magenta
            view.translatesAutoresizingMaskIntoConstraints = false
            return view
        }()
        
        let labelOne: UILabel = {
            let label = UILabel()
            label.text = "Scroll Top"
            label.backgroundColor = .red
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
        
        let labelTwo: UILabel = {
            let label = UILabel()
            label.text = "Scroll Bottom"
            label.backgroundColor = .green
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
        
        let scrollView: UIScrollView = {
            let v = UIScrollView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .cyan
            return v
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .yellow
            
            // add the scroll view
            self.view.addSubview(scrollView)
            
            // add contentView to scroll view
            scrollView.addSubview(contentView)
            
            // add two labels to contentView
            contentView.addSubview(labelOne)
            contentView.addSubview(labelTwo)
                    
            // respect safe-area
            let g = view.safeAreaLayoutGuide
            
            // if you want to ignore the safe-area (bad idea),
            //  use this instead
            //let g = view!
            //scrollView.contentInsetAdjustmentBehavior = .never
            
            // we're going to constrain the contentView to the scroll view's content layout guide
            let scg = scrollView.contentLayoutGuide
            
            NSLayoutConstraint.activate([
                
                // constrain scrollView Top / Leading / Trailing / Bottom to view (safe-area)
                //  with 8-pts on each side
                scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
                scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
                scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
                scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
                
                // constrain contentView Top / Leading / Trailing / Bottom to scroll view's Content Layout Guide
                //  with 16-pts on each side
                contentView.topAnchor.constraint(equalTo: scg.topAnchor, constant: 16.0),
                contentView.leadingAnchor.constraint(equalTo: scg.leadingAnchor, constant: 16.0),
                contentView.trailingAnchor.constraint(equalTo: scg.trailingAnchor, constant: -16.0),
                contentView.bottomAnchor.constraint(equalTo: scg.bottomAnchor, constant: -16.0),
    
                // if we only want vertical scrolling, constrain contentView Width
                // to scrollView's Frame Layout Guide minus 32-pts (because we have 16-pts on each side)
                contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: -32.0),
                
                // constrain labelOne Top / Leading 16-pts to contentView Top / Leading
                //  (so it shows up at top-left corner)
                labelOne.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16.0),
                labelOne.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16.0),
                
                // constrain labelTwo Bottom / Trailing 16-pts to contentView Bottom / Trailing
                //  (so it shows up at bottom-right corner)
                labelTwo.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16.0),
                labelTwo.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16.0),
                
                // constrain labelTwo Top to labelOne Bottom + 1500-pts
                //  so we'll have some vertical scrolling to get to it
                labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 1500.0),
                
            ])
                    
        }
        
    }