【问题标题】:Fitting UIStackView in UIScrollView programmatically以编程方式在 UIScrollView 中安装 UIStackView
【发布时间】:2021-05-31 07:16:58
【问题描述】:

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

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

感谢任何建议。现在已经为此工作了2天。谢谢!

【问题讨论】:

  • 如果图像的宽度为 50 磅并且堆栈视图/滚动视图是主视图的宽度,为什么堆栈视图只会显示 3 个图像?所有这些都适合主视图,因此所有这些都立即可见。仅当内容大于滚动视图时,滚动视图才会滚动。否则没有理由滚动。
  • 我也不清楚堆栈视图在这个故事中的用途。对我来说,这听起来更像是一个集合视图......
  • 您是否在 Storyboard 中设置视图?还是通过代码?
  • @matt 我正在尝试制作一个带有 3 个可见选项卡的水平栏(如果您愿意的话,可以使用选项卡栏),并且在超过 3 个选项卡可用时可滚动。

标签: ios swift user-interface uiscrollview uistackview


【解决方案1】:

你可能想做什么......

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

创建一个“标签视图” - 这是一个带有50 x 50 居中图像视图、圆角顶角和 1-pt 轮廓的示例:

我们可以用这个简单的类来创建它:

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
    }

}

对于我们添加到堆栈视图的每个“选项卡”,我们将设置其宽度约束等于滚动视图的框架布局指南 widthAnchormultiplier: 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
        }
        
    }

}

试一试,看看是否是你想要的。

【讨论】:

  • 非常有帮助!太感谢了!这符合我的需要。我让它根据你的回答水平滚动,但堆栈弄乱了它里面的视图(见附件)。必须弄清楚,但再次感谢!
  • @JadielAlfonso -- "...弄乱了里面的视图(见附件)" ...你的意思是包含屏幕截图吗?
  • 我做了,但显然没有附上文件:)。不过,我根据你的回答让它工作了。我试图弄清楚如何将 MyTabView 作为 VC 的一部分。我的实现定义了一个布局(包括 MyTabView、堆栈和 scrollView + 逻辑)和一个构造函数,当需要选项卡时将调用该构造函数。问题是我需要定义实际的 ImageView 是 50X50。不过你成功了,再次感谢!
猜你喜欢
  • 2021-11-14
  • 1970-01-01
  • 1970-01-01
  • 2017-10-11
  • 2020-10-16
  • 2019-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多