0

recently I tried to set up a dynamic UIScrollView according to this website : https://medium.com/@javedmultani16/uiscrollview-dynamic-content-size-through-storyboard-in-ios-fb873e9278e

I tried to make one step by step, but seems like I have misunderstood something but I can't figure it out. Here is my code, I tried to add 30 UITextField and set the UIScrollView equal height with those contents.

Problem I met, the scrollview not work correctly, it can only scroll a little bit, like I can only scroll to about 8 or 9 line , the others below I can't scroll down.

    override func viewDidLoad() {
    super.viewDidLoad()
    
    //Step 1
    let scrollview = UIScrollView()
    view.addSubview(scrollview)
    scrollview.translatesAutoresizingMaskIntoConstraints = false
    scrollview.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    scrollview.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    scrollview.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    scrollview.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    scrollview.alwaysBounceVertical = true
    
    //Step 2
    let oneview = UIView()
    scrollview.addSubview(oneview)
    oneview.translatesAutoresizingMaskIntoConstraints = false
    oneview.topAnchor.constraint(equalTo: scrollview.topAnchor).isActive = true
    oneview.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor).isActive = true
    oneview.leadingAnchor.constraint(equalTo: scrollview.leadingAnchor).isActive = true
    oneview.trailingAnchor.constraint(equalTo: scrollview.trailingAnchor).isActive = true
    oneview.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
    let heightConstraint = oneview.heightAnchor.constraint(equalTo: view.heightAnchor)
    heightConstraint.isActive = true
    heightConstraint.priority = .defaultLow
    
    //Step 3
    for i in 1...30{
        let field = UITextField()
        field.placeholder = "This is line "+String(i+1)
        field.backgroundColor = .gray
        field.isUserInteractionEnabled = false
        oneview.addSubview(field)
        field.translatesAutoresizingMaskIntoConstraints = false
        field.widthAnchor.constraint(equalTo: oneview.widthAnchor, multiplier: 0.8).isActive = true
        field.heightAnchor.constraint(equalToConstant: 50).isActive = true
        field.centerXAnchor.constraint(equalTo: oneview.centerXAnchor).isActive = true
        field.topAnchor.constraint(equalTo: oneview.topAnchor, constant: CGFloat(100*i)).isActive = true
    }
}
3
  • What problem did you meet? Commented Sep 21, 2020 at 8:45
  • Sorry , I have update the problem I met up the context . Thanks for reminding. Commented Sep 21, 2020 at 9:09
  • You should set the top anchor constraint for each textfield so that it is equal to the bottom anchor constraint of the previous textfield. And let the last textfield's bottom constraint be equal to the bottom constraint of the oneview. Commented Sep 21, 2020 at 9:15

2 Answers 2

1

The whole point of using this dummy subview (oneview in your code) is so that its contents can tell the dummy subview what height it should be, and the scroll view's content size will fit that height.

However, your textfield's constraints do not suggest a height for oneview!. All your textfields' constraints say is the text fields

  • should be some distance below the top of oneview.
  • should be a certain height
  • should have the same centre X as oneview
  • should have the same width as oneview

To satisfy the above, oneview's height doesn't need to change at all. The layout engine can just place your text fields outside of the bounds of oneview, and still satisfy the above constraints. (Think about it!)

But, if you add one more constraint to the last text field, that it

should be a certain distance above the bottom of oneview

then oneview has to resize in order to satisfy that. You are indirectly implying a height for oneview, because you are saying

for the last text field, I want this much space to be above it, and this much space to be below it.

We could just make that "certain distance" zero as well. Here's how you would do it in code:

// in the loop...
if i == 30 {
    field.bottomAnchor.constraint(equalTo: oneview.bottomAnchor).isActive = true
}

An even better way to tell oneview its height would be to use a UIStackView. Replace steps 2 and 3 with:

    let oneview = UIStackView()
    oneview.alignment = .fill
    oneview.distribution = .equalSpacing
    oneview.spacing = 50
    oneview.axis = .vertical
    scrollview.addSubview(oneview)
    oneview.translatesAutoresizingMaskIntoConstraints = false
    oneview.topAnchor.constraint(equalTo: scrollview.topAnchor).isActive = true
    oneview.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor).isActive = true
    oneview.leadingAnchor.constraint(equalTo: scrollview.leadingAnchor).isActive = true
    oneview.trailingAnchor.constraint(equalTo: scrollview.trailingAnchor).isActive = true
    oneview.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
    let heightConstraint = oneview.heightAnchor.constraint(equalTo: view.heightAnchor, constant: 25)
    heightConstraint.isActive = true
    heightConstraint.priority = .defaultLow
    
    for i in 1...30{
        let field = UITextField()
        field.placeholder = "This is line "+String(i+1)
        field.backgroundColor = .gray
        field.isUserInteractionEnabled = false
        oneview.addArrangedSubview(field)
        field.translatesAutoresizingMaskIntoConstraints = false
        field.heightAnchor.constraint(equalToConstant: 50).isActive = true
    }
Sign up to request clarification or add additional context in comments.

1 Comment

thanks a lot. After thinking some points you pointed out, I have understood how it works !
0

The problem is the first top view should be anchored to the top of the content view and the last bottom view should anchored to the bottom of the contentView

Extensions

extension UIScrollView {

func assignKeyboardObservers() {
    
    NotificationCenter.default.addObserver(
        
        self,
        
        selector: #selector(keyboardWillShow),
        
        name: UIResponder.keyboardWillShowNotification,
        
        object: nil
        
    )
    
    NotificationCenter.default.addObserver(
        
        self,
        
        selector: #selector(keyboardWillDissmiss),
        
        name: UIResponder.keyboardWillHideNotification,
        
        object: nil
        
    )
    
}



@objc fileprivate func keyboardWillShow(_ notification: Notification) {
    
    if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
        
        let keyboardRectangle = keyboardFrame.cgRectValue
        
        let keyboardHeight = keyboardRectangle.height
        
        insetScrollView(insetBottom: keyboardHeight)
    }
    
}



@objc fileprivate func keyboardWillDissmiss(_ notification: Notification) {
    
    insetScrollView(insetBottom: 0)
    
}



fileprivate func insetScrollView(insetBottom: CGFloat) {
    
    var inset:UIEdgeInsets = contentInset
    
    inset.bottom = insetBottom
    
    
    
    UIView.animate(withDuration: 0.2, animations: {
        
        self.contentInset = inset
        
    })
    
}

}

extension UIViewController {
func addScrollViewToView(paddingContentView:UIEdgeInsets = .zero, anchorScroll:( _ scrollView: inout UIScrollView)->())->UIView {

    let tapToDismiss = UITapGestureRecognizer(target: self, action: #selector(dimissKeyboard(_:)))
    tapToDismiss.cancelsTouchesInView = false
    view.addGestureRecognizer(tapToDismiss)
    var scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.bounces = true
    scrollView.isScrollEnabled = true
    scrollView.assignKeyboardObservers()
    let contentView = UIView()
    contentView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(scrollView)
    anchorScroll(&scrollView)
    scrollView.addSubview(contentView)
    NSLayoutConstraint.activate([
        contentView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: paddingContentView.top),
        contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: paddingContentView.left),
        contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -paddingContentView.right),
        contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -paddingContentView.bottom),
        contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
        contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
        ])
    return contentView
}

@objc fileprivate func dimissKeyboard(_ sender: UITapGestureRecognizer) {
    view.endEditing(true)
}
}

How to use it

import UIKit

 class TESTViewController: UIViewController {

private var contentView:UIView!


override func viewDidLoad() {
    super.viewDidLoad()

    contentView = addScrollViewToView(anchorScroll: { (scrollView) in
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
            scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
        ])
    })
    
    let stackView:UIStackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .vertical
    stackView.spacing = 3
    stackView.alignment = .fill
    stackView.distribution = .fillEqually
    
    
    for i in 1...30{
        let field = UITextField()
        field.translatesAutoresizingMaskIntoConstraints = false
        field.placeholder = "This is line "+String(i+1)
        field.backgroundColor = .gray
        field.isUserInteractionEnabled = false
        field.heightAnchor.constraint(equalToConstant: 50).isActive = true
        stackView.addSubview(field)
    }
    
    contentView.addSubview(stackView)
    NSLayoutConstraint.activate([
        stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
        stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
        stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
        stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
    ])
}

}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.