简体   繁体   English

在UIScrollView中调整多行UILabel的大小会中断滚动。 为什么?

[英]Resizing a multi line UILabel within a UIScrollView breaks scrolling. Why?

I use the following setup: 我使用以下设置:

  • The ViewControllers view holds a UIScrollView with Top, Leading, Trailing and Bottom constraints to match the VCs size ViewControllers视图包含一个UIScrollView具有顶部,前导,尾随和底部约束以匹配VC的大小
  • The ScrollView contains two subviews: ScrollView包含两个子视图:
    • A UIView to define the content size of the scroll view. 一个UIView用于定义滚动视图的内容大小。 It has the same height as the ScrollView but twice its width. 它具有与ScrollView相同的高度,但其宽度是其两倍。 Thus only horizontal scrolling is possible. 因此,只能进行水平滚动。
    • A UILabel with some long text with a Height and Width constraint to set a fixed size and a Top and Leading constraint to the ScrollView to set a fixed position. 一个UILabel带有一些带有Height和Width约束的长文本,用于设置固定大小,以及对ScrollView设置Top和Leading约束以设置固定位置。
  • The width of the Label is changed when the ScrollView scrolls. ScrollView滚动时,Label的宽度会更改。

Problem: If the Label is set to use more than one line AND the ScrollViews contenOffset property is set manually the ScrollView stops scrolling. 问题:如果Label设置为使用多行,并且ScrollViews contenOffset属性是手动设置的,则ScrollView停止滚动。

ViewController View
+---------------------+
|+-------------------+| 
||ScrollView         ||
||+------------------||--------------------+
|||UIView to define  || content size       |
|||                  ||                    |
|||                  ||                    |
|||  [MultiLine]     ||                    |
|||  [  Label  ]     ||                    |
|||                  ||                    |
||+------------------||--------------------+
|+-------------------+|
+---------------------+


- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // Setting the ContentOffset will stop scrolling 
    //[self.scrollView setContentOffset:CGPointMake(0, 0)];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // Resize Label when scrolling
    self.labelWidthConstraint.constant = MAX (50, 50 + self.scrollView.contentOffset.x);
}

Resizing the label using this code works fine if 如果以下情况,使用此代码调整标签大小可以正常工作

  • the label is set use one line . 标签设置为一行 In this case setting the setting the content offset does NOT do any harm. 在这种情况下,设置内容偏移量不会造成任何损害。 OR 要么
  • the content offset is not changes (not even set to (0, 0) ). 内容偏移量不会改变(甚至不会设置为(0, 0) )。 In this case setting the label to multi line does NOT do any harm 在这种情况下,将标签设置为多行不会有任何危害

Setting the content offset AND using multi line at the same time DOES NOT work. 设置内容偏移并采用多线同时不起作用 The scroll cannot be scrolled any more. 滚动无法再滚动。

Why is this? 为什么是这样? Any idea what might cause this and how to solve it? 任何想法可能导致此问题以及如何解决?

The problem is here: 问题在这里:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // Setting the ContentOffset will stop scrolling 
    [self.scrollView setContentOffset:CGPointMake(0, 0)];
}

Changing self.labelWidthConstraint.constant when scrolling the scroll view triggers viewDidLayoutSubviews ! 滚动滚动视图时更改self.labelWidthConstraint.constant 会触发 viewDidLayoutSubviews So, as soon as you start scrolling, your code immediately resets .contentOffset to 0,0 . 因此,一旦开始滚动,您的代码就会立即将.contentOffset重置为0,0

I don't know why you would want to be calling setContentOffset anyway, certainly not in viewDidLayoutSubviews . 我不知道为什么您无论如何都要调用setContentOffset ,当然不可以在viewDidLayoutSubviews调用。

Doing a quick test, after removing the viewDidLayoutSubviews code... 删除viewDidLayoutSubviews代码后,进行快速测试...

I called [self.scrollView setContentOffset:CGPointMake(0, 0)]; 我叫[self.scrollView setContentOffset:CGPointMake(0, 0)]; at the end of viewDidload (and also tried in viewDidAppear )... the scrolling (and constraint constant updating) continues to work fine. viewDidload的末尾(并在viewDidAppear也尝试过)...滚动(和约束常量更新)继续正常进行。

I also added a button that calls [self.scrollView setContentOffset:CGPointMake(0, 0)]; 我还添加了一个调用[self.scrollView setContentOffset:CGPointMake(0, 0)];的按钮[self.scrollView setContentOffset:CGPointMake(0, 0)]; when tapped... the scrolling (and constraint constant updating) continues to work fine. 点击时...滚动(和约束常量更新)继续正常工作。

The issue appears to be that when the label constraints are changed it triggers viewDidLayoutSubviews which then sets the UIScrollView to not scroll since set contentOffset is then called over and over. 问题似乎是,当更改标签约束时,它会触发viewDidLayoutSubviews,然后将UIScrollView设置为不滚动,因为随后会反复调用set contentOffset。 You could overcome this if you are only wanting to set the UIScrollView to CGPoint.zero on the initial layout by using a bool as a flag. 如果您只想通过使用bool作为标志在初始布局上将UIScrollView设置为CGPoint.zero,则可以克服此问题。 Apparently since UILabel needs a redraw on size changes it triggers viewDidLayoutSubviews. 显然,由于UILabel需要重新绘制尺寸更改,因此会触发viewDidLayoutSubviews。 Here is an example in Swift. 这是Swift中的示例。

import UIKit

class ViewController: UIViewController {

    lazy var scrollView : UIScrollView = {
        let sv = UIScrollView(frame: self.view.bounds)
        sv.translatesAutoresizingMaskIntoConstraints = false
        sv.delegate = self
        return sv
    }()

    lazy var contentView : UIView = {
        let v = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width * 4, height: self.view.bounds.height))
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    lazy var label : UILabel = {
        let lbl = UILabel(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 50))
        lbl.numberOfLines = 0
        lbl.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s"
        lbl.minimumScaleFactor = 0.5
        lbl.adjustsFontSizeToFitWidth = true
        lbl.font = UIFont.systemFont(ofSize: 22)
        lbl.translatesAutoresizingMaskIntoConstraints = false
        return lbl
    }()

    var widthConstraint : NSLayoutConstraint?
    var heightConstraint : NSLayoutConstraint?
    var startingHeight : CGFloat = 0
    var startingWidth : CGFloat = 0
    override func viewDidLoad() {
        super.viewDidLoad()
        //first scrollview
        self.view.addSubview(scrollView)
        pinToAllSides(target: scrollView)

        //now content view
        self.scrollView.addSubview(contentView)
        contentView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 2).isActive = true
        contentView.heightAnchor.constraint(equalTo: self.scrollView.heightAnchor, multiplier: 1).isActive = true
        contentView.backgroundColor = .green
        pinToAllSides(target: contentView)
        scrollView.layoutIfNeeded()

        //now the label
        self.scrollView.addSubview(label)
        label.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor, constant: 20).isActive = true
        label.topAnchor.constraint(equalTo: self.scrollView.topAnchor, constant: 60).isActive = true
        label.backgroundColor = .red
        widthConstraint = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.view.bounds.width/2)

        heightConstraint = NSLayoutConstraint(item: label, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 300)
        if let wc = widthConstraint,
            let hc = heightConstraint{
            startingHeight = hc.constant
            startingWidth = wc.constant
            label.addConstraint(wc)
            label.addConstraint(hc)
        }

    }

    func pinToAllSides(target:UIView){
        guard let superview = target.superview else{
            return
        }
        target.leadingAnchor.constraint(equalTo: superview.leadingAnchor).isActive = true
        target.trailingAnchor.constraint(equalTo: superview.trailingAnchor).isActive = true
        target.topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
        target.bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
    }

    var hasHappenedOnce : Bool = false
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if hasHappenedOnce == false{
            hasHappenedOnce = true
            self.scrollView.contentOffset = .zero
        }
    }
}

extension ViewController : UIScrollViewDelegate{

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        //hopefully it is laggy due to simulator but for the label i would ditch constraints myself
        self.widthConstraint?.constant = max(startingWidth, self.scrollView.contentOffset.x * 1.1 + startingWidth)

        let height = startingHeight - self.scrollView.contentOffset.x
        self.heightConstraint?.constant = height
        label.updateConstraints()
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM