使用带有最小内容顶部锚点的 UIScrollView 会导致视觉故障

Using UIScrollView with a minimum content top anchor causes visual glitch

我有一个滚动视图,其中有一个内容视图。我将滚动视图的顶部锚点设置在图像底部的正上方。我将内容视图的顶部锚点设置为实际上位于图像的底部。这样你就可以下拉内容并显示到图像的底部,而不能进一步下拉内容视图。但是,这会导致内容跳转。

这是我的代码:

class HomeParallaxScrollViewController: UIViewController {

    private let topImageView = UIImageView(image: UIImage(named: "cat"))
    private let contentView = UIView()
    private let scrollView = UIScrollView()
    private let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .gray

        topImageView.contentMode = .scaleAspectFill
        contentView.backgroundColor = .white
        label.text = "SOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT"
        label.textColor = .black
        label.numberOfLines = 0

        [contentView, label, topImageView, scrollView].forEach { [=10=].translatesAutoresizingMaskIntoConstraints = false }

        scrollView.addSubview(contentView)
        contentView.addSubview(label)
        view.addSubview(topImageView)
        view.addSubview(scrollView)

        NSLayoutConstraint.activate([
            topImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            topImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            topImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            topImageView.heightAnchor.constraint(equalToConstant: 200),

            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.widthAnchor.constraint(equalTo: view.widthAnchor),
            scrollView.topAnchor.constraint(equalTo: topImageView.bottomAnchor, constant: -30),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.topAnchor.constraint(lessThanOrEqualTo: topImageView.bottomAnchor), //This is what's causing the glitch

            label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            label.topAnchor.constraint(equalTo: contentView.topAnchor),
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
    }
}

这是正在发生的事情:

尝试添加另一个顶部约束——尤其是添加到滚动视图之外的元素——是个坏主意,而且如您所见,这是行不通的。我确定您注意到正在生成自动布局冲突消息。

一种方法是实现 scrollViewDidScroll 委托函数:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    // limit drag-down in the scroll view to the overlap size
    scrollView.contentOffset.y = max(scrollView.contentOffset.y, -30)
}

当用户向下拖动滚动时,它会在 30 点处停止。

这是您的示例,稍作修改 -- 我没有您的 .plBackgroundLightGray.PLSemiboldFont,我为顶部图像视图添加了图像加载 -- 但这应该 运行原样:

// conform to UIScrollViewDelegate
class HomeParallaxScrollViewController: UIViewController, UIScrollViewDelegate {

    private let topImageView = UIImageView(image: UIImage(named: "cat"))
    private let contentView = UIView()
    private let scrollView = UIScrollView()
    private let label = UILabel()

    // this will be the "overlap" of the scroll view and top image view
    private var scrollOverlap: CGFloat = 30.0

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // limit drag-down in the scroll view to scrollOverlap points
        scrollView.contentOffset.y = max(scrollView.contentOffset.y, -scrollOverlap)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .lightGray // .plBackgroundLightGray

        topImageView.contentMode = .scaleAspectFill
        if let img = UIImage(named: "background") {
            topImageView.image = img
        }
        contentView.backgroundColor = .white
        label.text = "SOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT\n\n\nSOME\n\n\nRANDOM\n\n\nCONTENT"
        label.font = UIFont.boldSystemFont(ofSize: 16) // .PLSemiboldFont(size: 16)
        label.textColor = .black
        label.numberOfLines = 0

        [contentView, label, topImageView, scrollView].forEach { [=11=].translatesAutoresizingMaskIntoConstraints = false }

        scrollView.addSubview(contentView)
        contentView.addSubview(label)
        view.addSubview(topImageView)
        view.addSubview(scrollView)

        NSLayoutConstraint.activate([
            topImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            topImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            topImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            topImageView.heightAnchor.constraint(equalToConstant: 200),

            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.widthAnchor.constraint(equalTo: view.widthAnchor),
            scrollView.topAnchor.constraint(equalTo: topImageView.bottomAnchor, constant: scrollOverlap),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),

            // nope, not a good idea -- will cause constraint conflicts
            //contentView.topAnchor.constraint(lessThanOrEqualTo: topImageView.bottomAnchor), //This is what's causing the glitch

            label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            label.topAnchor.constraint(equalTo: contentView.topAnchor),
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])

        // set delegate to self
        scrollView.delegate = self
    }

}