【问题标题】:UIScrollView stops scrolling with Autolayout or after zoomingUIScrollView 在 Autolayout 或缩放后停止滚动
【发布时间】:2021-02-14 06:42:36
【问题描述】:

我有以下代码来设置 UIScrollView 的子视图。我首先创建了一个名为 MyScrollView 的 UIScrollView 子类。然后我添加一个名为 contentView 的视图作为子视图。我将所有其他视图添加到滚动视图作为此 contentView 的子视图。问题是在使用自动布局约束设置以下代码后,scrollView 不滚动。

public class MyScrollView:UIScrollView {


  private var contentView:UIView!

   override init(frame: CGRect) {
    super.init(frame: frame)
    self.translatesAutoresizingMaskIntoConstraints = false
    self.isScrollEnabled = true
    self.isDirectionalLockEnabled = true
    self.showsHorizontalScrollIndicator = true
    self.showsVerticalScrollIndicator = false
    self.decelerationRate = .normal
    self.delaysContentTouches = false
    self.bouncesZoom = true
    
    setupSubviews()
}


  private func setupSubviews() {

     contentView = UIView()
    contentView.backgroundColor = UIColor.clear
    contentView.translatesAutoresizingMaskIntoConstraints = false
    contentView.isUserInteractionEnabled = true
    
    self.addSubview(contentView)
    
    contentView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
    contentView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
    contentView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    contentView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: CGFloat(19), constant: CGFloat(5)).isActive = true
    contentView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 2.0, constant: CGFloat(5)).isActive = true
    
   // self.contentSize = CGSize(width: 10000, height: 2*frame.height)


    let subviewWidth = CGFloat(450)
    let subviewHeight = CGFloat(20)

    //Add other subviews to contentView
    (0...4).compactMap { i in
        (0...19).compactMap { j in
             let x = CGFloat(j)*(subviewWidth + 5.0) + 5.0
            let y = CGFloat(i)*(subviewHeight + 5.0) + 5.0
             let subview = UIView(frame: CGRect(x: x, y: y, width: subviewWidth, height: subviewHeight))
                contentView.addSubview(subview)
      }
  }

    

问题是当自动布局约束如上所述设置时,滚动视图不会滚动。如果没有设置约束,它会滚动,但在缩放后,滚动会再次被禁用。我只是在滚动视图委托中设置了这个。

  public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
    return scrollView.subviews.first //contentView
}

【问题讨论】:

  • 做一些额外的搜索/研究以了解UIScrollView 的工作原理。如果您为滚动视图的内容使用自动布局(您应该这样做),您永远不想设置.contentSize——这是错误的。
  • 好的,我需要在向 UIScrollView 添加更多子视图时动态更改 contentView 的宽度和高度锚点。但是现在似乎 contentSize 在我设置约束时没有立即更新。由于各种原因,我需要查询 contentSize 属性。我需要等待查询吗?
  • 要获得这方面的帮助,您需要显示一些代码。 “当我添加更多子视图时,我需要动态改变 contentView 的宽度和高度锚点” ...这是一项非常常见的任务...你是怎么做到的?添加子视图时是否正确更改约束?没有看到你在做什么(你的代码),就没有办法告诉你你做错了什么。
  • 我更新了问题。截至目前,我只是按照所示方式在循环中添加子视图。稍后,我会动态添加或删除这些子视图。
  • 那么...您没有为要添加的子视图使用自动布局吗?

标签: ios swift uiscrollview autolayout


【解决方案1】:

对于您的方法...

您希望将contentView 限制在滚动视图的内容布局指南中。这将自动确定“可滚动”区域。

由于您没有为 contentView 的子视图使用自动布局,因此每次添加新子视图时都需要更新 .contentView 宽度和高度约束。

这是一个例子。我们将创建一个MyScrollView,距离顶部/前导/尾随 40 分,高度为 240 分。

子视图有绿色背景,内容视图有蓝色背景,滚动视图有红色背景(这样我们可以很容易地看到框架)。

我们将从一个子视图开始,以便轻松查看发生的情况。一开始,只有一个小子视图,不会滚动。

每次点击任意位置时,我们都会向 contentView 添加一个新的 Subview,背景为黄色,并根据需要更新 contentView 的宽度和高度约束。您会看到蓝色内容视图变大以匹配子视图。一旦子视图导致内容视图大于滚动视图的宽度或高度,滚动就会自动进行。

public class MyScrollView: UIScrollView {
    
    private var contentView:UIView!
    
    // contentView's Width and Height constraints
    //  we'll update the .constant values when we add subviews
    private var cvWidthConstraint: NSLayoutConstraint!
    private var cvHeightConstraint: NSLayoutConstraint!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        self.translatesAutoresizingMaskIntoConstraints = false
        self.isScrollEnabled = true
        self.isDirectionalLockEnabled = true
        self.showsHorizontalScrollIndicator = true
        self.showsVerticalScrollIndicator = false
        self.decelerationRate = .normal
        self.delaysContentTouches = false
        self.bouncesZoom = true
        
        setupSubviews()
        
    }
    
    private func setupSubviews() {
        
        contentView = UIView()
        contentView.backgroundColor = UIColor.clear
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentView.isUserInteractionEnabled = true
        
        self.addSubview(contentView)
        
        // constrain contentView to scroll view's Content Layout Guide
        //  this determines the "scrollable" area
        contentView.leadingAnchor.constraint(equalTo: self.contentLayoutGuide.leadingAnchor).isActive = true
        contentView.trailingAnchor.constraint(equalTo: self.contentLayoutGuide.trailingAnchor).isActive = true
        contentView.topAnchor.constraint(equalTo: self.contentLayoutGuide.topAnchor).isActive = true
        contentView.bottomAnchor.constraint(equalTo: self.contentLayoutGuide.bottomAnchor).isActive = true

        // create contentView's Width and Height constraints
        cvWidthConstraint = contentView.widthAnchor.constraint(equalToConstant: 0.0)
        cvHeightConstraint = contentView.heightAnchor.constraint(equalToConstant: 0.0)
        
        // activate them
        cvWidthConstraint.isActive = true
        cvHeightConstraint.isActive = true
        
        //Add other subviews to contentView
        
        // we'll start with ONE subview, so we can easily see what's happening
        let subviewWidth = CGFloat(240)
        let subviewHeight = CGFloat(20)

        let subview = UILabel(frame: CGRect(x: 5, y: 5, width: subviewWidth, height: subviewHeight))
        subview.textAlignment = .center
        subview.text = "First"
        subview.backgroundColor = .green
        contentView.addSubview(subview)

        // so we can see the frames
        self.backgroundColor = .red
        self.contentView.backgroundColor = .blue
        
        // update the contentView constraints
        updateContent()
    }
    
    private func updateContent() -> Void {
        // array of subviews
        let views = contentView.subviews
        // get the
        //  max Y of the subview frames
        //  max X of the subview frames
        guard let maxYValue = views.lazy.map({ $0.frame.maxY }).max(),
              let maxXValue = views.lazy.map({ $0.frame.maxX }).max()
        else { return }
        
        // update contentView Width and Height constraints
        cvWidthConstraint.constant = maxXValue + 5.0
        cvHeightConstraint.constant = maxYValue + 5.0
    }
    
    func addLabel(frame _frame: CGRect, text: String) -> Void {

        // add a new subview
        let subview = UILabel(frame: _frame)
        subview.textAlignment = .center
        subview.text = text
        subview.backgroundColor = .yellow
        contentView.addSubview(subview)
        
        // update the contentView constraints
        updateContent()

    }
}


class ExampleViewController: UIViewController {
    
    let myScrollView = MyScrollView()
    
    var count: Int = 1
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(myScrollView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([

            // constrain custom scroll view Top / Leading / Trailing
            //  40-pts from the safe-area edges
            myScrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            myScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            myScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            
            // scroll view Height: 240-pts
            myScrollView.heightAnchor.constraint(equalToConstant: 240.0),
        ])
        
        // add tap gesture recognizer so we can add a new subview
        //  every time we tap
        let t = UITapGestureRecognizer(target: self, action: #selector(self.testAddSubview))
        view.addGestureRecognizer(t)
    }
    
    @objc func testAddSubview() -> Void {
        let s = "New Subview \(count)"
        let x: CGFloat = CGFloat(count) * 60.0
        let y: CGFloat = CGFloat(count) * 35.0
        myScrollView.addLabel(frame: CGRect(x: x, y: y, width: 200, height: 30), text: s)
        count += 1
    }
}

启动时 - 一个子视图 - 无滚动:

添加一个新的子视图后 - 蓝色内容视图更大,但不足以滚动:

添加 4 个新的子视图后 - 现在我们可以滚动了:

【讨论】:

  • 完美,但问题仍然存在,即使您设置 cvWidthConstraint.constant = maxXValue + 5.0,启动时报告的 contentSize 仍为 0。这会在 setupSubviews() 方法从 updateContent() 调用返回后立即发生。
  • 我的解决方法是在 DispatchQueue.main.async { } 块中获取 contentSize。这样就有足够的时间来初始化滚动视图并设置 contentSize。
  • 不要设置.contentSize!!!您不需要这样做,如果这样做,您可能会造成问题。运行我给你的例子。你甚至可以放回你的循环来添加你的 5“行”,每行有 20 个子视图,你会看到蓝色内容视图自动调整大小并正确滚动。
  • 我正在阅读它,而不是设置它。需要它来设置您在另一个问题的答案中写的标尺视图的偏移量 - stackoverflow.com/questions/64541707/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-10
  • 1970-01-01
  • 1970-01-01
  • 2013-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多