以编程方式在 UIScrollView 中安装 UIStackView

Fitting UIStackView in UIScrollView programmatically

我将 stackView 添加到 scrollView。我将 scrollView 的宽度、X 和 Y 约束设置为与主视图相同,高度固定为 50。对于堆栈约束,我做了同样的事情,但相对于 scrollView 而不是视图。

我的问题是当我将 UIImageViews 添加到我的堆栈时(所有图像都是 50 x 50)。我需要堆栈只显示前三个 UIImageView,如果超过 3 个则水平滚动。到目前为止,我的堆栈始终显示所有 UIImageView。

如有任何建议,我们将不胜感激。现在已经为此工作了 2 天。谢谢!

你可能想做什么...

  • 将堆栈视图的所有 4 个边限制为滚动视图的内容布局指南
  • 将堆栈视图的高度限制为等于滚动视图的框架布局指南
  • 的高度
  • 不要限制堆栈视图的宽度
  • 将堆栈视图的分布设置为填充

创建“选项卡视图”- 这是一个 50 x 50 居中图像视图、圆顶角和 1 磅轮廓的示例:

我们可以用这个简单的方法创建它 class:

class MyTabView: UIView {
    
    let imgView = UIImageView()
    
    init(with image: UIImage) {
        super.init(frame: .zero)
        imgView.image = image
        commonInit()
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        imgView.translatesAutoresizingMaskIntoConstraints = false
        // light gray background
        backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        addSubview(imgView)
        NSLayoutConstraint.activate([
            // centered
            imgView.centerXAnchor.constraint(equalTo: centerXAnchor),
            imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
            // 50x50
            imgView.widthAnchor.constraint(equalToConstant: 50.0),
            imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
        ])
        
        // a little "styling" for the "tab"
        clipsToBounds = true
        layer.cornerRadius = 12
        layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        layer.borderWidth = 1
        layer.borderColor = UIColor.darkGray.cgColor
    }

}

对于我们添加到堆栈视图的每个“选项卡”,我们将其宽度约束设置为等于滚动视图的 Frame Layout Guide widthAnchor multiplier: 1.0 / 3.0。这样每个“选项卡视图”将是滚动视图宽度的 1/3:

如果有 1、2 或 3 个“制表符”,则不会进行水平滚动,因为它们都适合框架。

一旦我们有超过 3 个“选项卡”,堆栈视图的宽度将超过框架的宽度,我们将进行水平滚动:

这是我为此使用的视图控制器。它创建 9 个“标签图像”...以一个“标签”开始...每次点击都会添加一个“标签”,直到我们拥有全部 9 个,此时每次点击都会删除一个“标签”:

class StackAsTabsViewController: UIViewController {
    
    let stackView: UIStackView = {
        let v = UIStackView()
        v.axis = .horizontal
        v.distribution = .fill
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    let scrollView: UIScrollView = {
        let v = UIScrollView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    // a label to show what's going on
    let statusLabel: UILabel = {
        let v = UILabel()
        v.numberOfLines = 0
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    // array to hold our "tab" images
    var images: [UIImage] = []

    // we'll add a "tab" on each tap
    //  until we reach the end of the images array
    //  then we'll remove a "tab" on each tap
    //  until we're back to a single "tab"
    var isAdding: Bool = true

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // add the "status" label
        view.addSubview(statusLabel)
        
        // add stackView to scrollView
        scrollView.addSubview(stackView)
        
        // add scrollView to view
        view.addSubview(scrollView)
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        // scrollView Content and Frame Layout Guides
        let contentG = scrollView.contentLayoutGuide
        let frameG = scrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain scrollView Top / Leading / Trailing
            scrollView.topAnchor.constraint(equalTo: g.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            
            // height = 58 (image will be 50x50, so a little top and bottom padding)
            scrollView.heightAnchor.constraint(equalToConstant: 58.0),
            
            // constrain stackView all 4 sides to scrollView Content Layout Guide
            stackView.topAnchor.constraint(equalTo: contentG.topAnchor),
            stackView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
            
            // stackView Height equal to scrollView Frame Height
            stackView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
            
            // statusLabel in the middle of the view
            statusLabel.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 40.0),
            statusLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            statusLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0)
            
        ])
        
        // let's create 9 images using SF Symbols
        for i in 1...9 {
            guard let img = UIImage(systemName: "\(i).circle.fill") else {
                fatalError("Could not create images!!!")
            }
            images.append(img)
        }
        
        // add the first "tab view"
        self.updateTabs()
        
        // tap anywhere in the view
        let t = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
        view.addGestureRecognizer(t)

    }
    
    @objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
        updateTabs()
    }
    
    func updateTabs() -> Void {
        
        if isAdding {

            // get the next image from the array
            let img = images[stackView.arrangedSubviews.count]
            
            // create a "tab view"
            let tab = MyTabView(with: img)
            // add it to the stackView
            stackView.addArrangedSubview(tab)
            let frameG = scrollView.frameLayoutGuide
            NSLayoutConstraint.activate([
                // each "tab view" is 1/3rd the width of the scroll view frame
                tab.widthAnchor.constraint(equalTo: frameG.widthAnchor, multiplier: 1.0 / 3.0),
                // each "tab view" is the same height as the scroll view frame
                tab.heightAnchor.constraint(equalTo: frameG.heightAnchor),
            ])

        } else {

            stackView.arrangedSubviews.last?.removeFromSuperview()

        }

        if stackView.arrangedSubviews.count == 1 {
            isAdding = true
        } else if stackView.arrangedSubviews.count == images.count {
            isAdding = false
        }
        
        updateStatusLabel()
        
    }
    
    func updateStatusLabel() -> Void {
        
        // we'll do this async, to make sure the views have been updated
        DispatchQueue.main.async {
            let numTabs = self.stackView.arrangedSubviews.count
            var str = ""
            if self.isAdding {
                str += "Tap anywhere to ADD a tab"
            } else {
                str += "Tap anywhere to REMOVE a tab"
            }
            str += "\n\n"
            str += "Number of tabs: \(numTabs)"
            str += "\n\n"
            if numTabs > 3 {
                str += "Tabs WILL scroll"
            } else {
                str += "Tabs will NOT scroll"
            }
            self.statusLabel.text = str
        }
        
    }

}

试一试,看看这是否是您想要的。