iOS动态UIScrollView实现:动态增减子视图并调整高度
Hey there! Let's work through this UIScrollView dynamic update issue step by step— I totally get what you're aiming for: tapping the plus button on a view adds a new view below it, and this can repeat infinitely, with the scrollview adjusting to fit all the new content. Here's how to make it happen:
1. Lay a Solid Foundation for the ScrollView
First, you'll want a container view inside your UIScrollView (let's call it contentView). All your dynamically added views will live here— this makes calculating and updating the total height way easier. Set up these constraints:
- Pin
contentView's top, bottom, leading, and trailing edges to the UIScrollView's inner margins - Match
contentView's width to the UIScrollView's width (to avoid unwanted horizontal scrolling) - Pin the UIScrollView itself to its parent view's layout guides (like safe area) as needed
With this setup, the UIScrollView will automatically adjust its contentSize based on the contentView's height— no manual size calculations required later.
2. Build a Reusable View Component
Create a custom view (e.g., AddableItemView) that includes the plus button and any other content you need. Give it a fixed height (or let it auto-size based on internal content) so we know how much space each new view will take.
Example Swift code:
class AddableItemView: UIView { let plusButton = UIButton(type: .system) override init(frame: CGRect) { super.init(frame: frame) setupLayout() } required init?(coder: NSCoder) { super.init(coder: coder) setupLayout() } private func setupLayout() { // Style the view backgroundColor = .systemGray6 layer.cornerRadius = 8 clipsToBounds = true // Configure plus button plusButton.setTitle("+", for: .normal) plusButton.titleLabel?.font = .systemFont(ofSize: 24) plusButton.addTarget(self, action: #selector(plusTapped), for: .touchUpInside) // Add button constraints addSubview(plusButton) plusButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ plusButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), plusButton.centerYAnchor.constraint(equalTo: centerYAnchor), plusButton.widthAnchor.constraint(equalToConstant: 40), plusButton.heightAnchor.constraint(equalToConstant: 40), // Set fixed height for the item view (remove if you want auto-sizing) self.heightAnchor.constraint(equalToConstant: 60) ]) } // Use a closure to pass tap events to the parent controller var onPlusTap: (() -> Void)? @objc private func plusTapped() { onPlusTap?() } }
3. Implement Dynamic View Addition & Layout Updates
In your view controller, initialize the scrollview, contentView, and your initial secondView. Then add logic to insert new views whenever the plus button is tapped.
Example Swift code:
class ViewController: UIViewController { let scrollView = UIScrollView() let contentView = UIView() override func viewDidLoad() { super.viewDidLoad() setupScrollView() addInitialItemView() } private func setupScrollView() { view.addSubview(scrollView) scrollView.translatesAutoresizingMaskIntoConstraints = false scrollView.backgroundColor = .white scrollView.addSubview(contentView) contentView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ // ScrollView constraints scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), // ContentView constraints contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor) ]) } private func addInitialItemView() { let initialView = AddableItemView() initialView.onPlusTap = { [weak self] in self?.addNewItem(after: initialView) } contentView.addSubview(initialView) initialView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ initialView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), initialView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), initialView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), initialView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16) ]) } private func addNewItem(after previousView: AddableItemView) { let newView = AddableItemView() newView.onPlusTap = { [weak self] in self?.addNewItem(after: newView) } contentView.addSubview(newView) newView.translatesAutoresizingMaskIntoConstraints = false // Remove the old bottom constraint linking previous view to contentView NSLayoutConstraint.deactivate(contentView.constraints.filter { $0.firstItem === previousView && $0.secondItem === contentView && $0.firstAttribute == .bottom }) // Add constraints for the new view NSLayoutConstraint.activate([ newView.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: 16), newView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), newView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), newView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16) ]) // Trigger immediate layout update so the scrollview adjusts right away contentView.layoutIfNeeded() } }
Key Notes
- We let Auto Layout handle the scrollview's content size: by updating the
contentView's bottom constraint to point to the newest view, thecontentView's height grows automatically, and the scrollview follows suit. - Using closures to pass tap events means every new view can trigger the addition of another view, creating the infinite chain you want.
- If your views need to auto-size (e.g., for dynamic text), just remove the fixed height constraint from
AddableItemViewand let its internal content constraints define its height— the rest of the logic stays the same.
内容的提问来源于stack exchange,提问作者Aysel Abdullayeva

