如何将UICollectionView与UIView嵌入UIScrollView并实现整体滚动
Hey there! I’ve dealt with this exact layout challenge plenty of times, so let me break down how to get your UICollectionView and UIView scrolling smoothly together inside a UIScrollView.
Core Idea
The biggest gotcha with UIScrollView is that it needs a clear contentSize to know how far it can scroll. Instead of adding your UICollectionView and UIView directly to the scroll view, we’ll use a container UIView as the scroll view’s single child. All your content (top view + collection view) goes into this container, and we’ll use Auto Layout to let the container’s size drive the scroll view’s contentSize automatically.
Step-by-Step Implementation
1. Build the View Hierarchy
First, set up the base structure:
- Add a UIScrollView to your view controller’s main view, pinning it to all four edges (leading, trailing, top, bottom = 0).
- Add a container view (let’s call it
contentView) inside the scroll view. This is the key to making Auto Layout work correctly.
2. Configure Constraints for Scroll View & Container
For the scroll view and container, we need to link to the scroll view’s layout guides (critical for Auto Layout in scroll views):
// Code example (works for Storyboard too with similar constraints) let scrollView = UIScrollView() scrollView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(scrollView) let contentView = UIView() contentView.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(contentView) NSLayoutConstraint.activate([ // Pin scroll view to main view scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), scrollView.topAnchor.constraint(equalTo: view.topAnchor), scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), // Pin container to scroll view's content guide (defines content size) contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), // Lock container width to scroll view's frame (prevents horizontal scrolling, adjust if needed) contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor) ])
3. Add Your UIView & UICollectionView to the Container
Now add your custom top view and collection view to contentView:
Add the Top UIView
Pin it to the container’s top, leading, trailing, and set a fixed height (adjust to your needs):
let topInfoView = UIView() topInfoView.backgroundColor = .lightGray topInfoView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(topInfoView) NSLayoutConstraint.activate([ topInfoView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), topInfoView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), topInfoView.topAnchor.constraint(equalTo: contentView.topAnchor), topInfoView.heightAnchor.constraint(equalToConstant: 120) // Adjust height as needed ])
Add the UICollectionView
The trick here is making sure the collection view’s height matches its content size (so the scroll view knows how much space it needs). Plus, disable the collection view’s scrolling—we want the main scroll view to handle all scrolling:
let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: contentView.frame.width - 32, height: 80) // Adjust item size layout.minimumLineSpacing = 16 let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.backgroundColor = .white collectionView.isScrollEnabled = false // Critical: disable internal scrolling // Register your cell class and set dataSource/delegate here contentView.addSubview(collectionView) // Link collection view to top view and container NSLayoutConstraint.activate([ collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), collectionView.topAnchor.constraint(equalTo: topInfoView.bottomAnchor, constant: 16), collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16) ]) // Bind collection view height to its content size if #available(iOS 16.0, *) { // iOS 16+ has a built-in contentSizeAnchor for easy binding collectionView.heightAnchor.constraint(equalTo: collectionView.contentSizeAnchor.height).isActive = true } else { // For older iOS versions, use KVO to update height when content changes collectionView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil) // Set a temporary initial height (will be updated later) collectionView.heightAnchor.constraint(equalToConstant: 100).isActive = true }
Handle KVO for iOS <16
If you need to support older iOS versions, add this observer logic to your view controller:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "contentSize", let collectionView = object as? UICollectionView else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } // Update the height constraint to match the current content size collectionView.heightAnchor.constraint(equalToConstant: collectionView.contentSize.height).isActive = true } // Don't forget to remove the observer when the view controller is deinitialized deinit { collectionView.removeObserver(self, forKeyPath: "contentSize") }
Key Notes to Avoid Issues
- Disable collection view scrolling: If you skip
collectionView.isScrollEnabled = false, users will get stuck trying to scroll the collection view instead of the whole page. - Always use a container view: Directly adding content to UIScrollView breaks Auto Layout’s ability to calculate
contentSizecorrectly. - Test on real devices: Sometimes layout quirks show up on physical devices that don’t appear in the simulator.
If you run into any weird layout glitches, double-check your constraints—9 times out of 10, it’s a missing or conflicting constraint somewhere!
内容的提问来源于stack exchange,提问作者MillerAiOS

