UIScrollView可滚动内容大小的歧义

624

各位开发者们, 我在Interface Builder中(Xcode 5 / iOS 7)遇到了AutoLayout的问题。 这是非常基础且重要的,因此我认为每个人都应该知道如何正确地使用它。如果这是Xcode中的一个bug,那么这是一个关键性的问题!

因此,每当我有像这样的视图层次结构时,我就会遇到麻烦:

>UIViewController
>> UIView
>>>UIScrollView
>>>>UILabel (or any other comparable UIKit Element)

UIScrollView的约束很稳定,例如:距每一侧50px(没有问题)。 然后我给UILabel添加了一个Top Space约束(没有问题)(我甚至可以固定标签的高度/宽度,这并没有改变什么,但由于标签的内在大小应该是不必要的)

当我给UILabel添加一个trailing constraint时问题就开始了:

例如,Trailing Space to: Superview Equals: 25

现在会发生两个警告 - 我不明白为什么:

A)可滚动内容大小不明确(滚动视图具有不明确的可滚动内容高度/宽度)

B)未放置正确的视图(Label Expected: x=-67 Actual:x=207)

我在全新的项目中进行了这个最小化的例子,你可以下载,并且我附上了一个屏幕截图。正如你所看到的,界面生成器希望Label坐落在UIScrollView的边界之外(橙色虚线矩形)。使用Resolve Issues Tool更新标签的框架将其移动到那里。

请注意:如果您用UIView替换UIScrollView,则行为与预期相符( Label的帧是正确的,符合约束),因此UIScrollView似乎存在问题或者我漏掉了一些重要内容。

如果我不像IB建议的那样更新标签框架,就可以很好地定位它,正好在其应该处的位置,并且UIScrollView是可滚动的。 如果我更新标签,则标签被隐藏,UIScrollView无法滚动。

帮帮我,欧比万·克诺比!为什么出现模糊的布局? 为什么视图被放错了?

你可以在这里下载示例项目并尝试找出问题: https://github.com/Wirsing84/AutoLayoutProblem

Interface Builder中问题的说明


9
尝试将UILabel放置在内容视图中,然后将标签的尾边设置为内容视图(普通UIView)。这个视频可能会对你有很大帮助:http://www.youtube.com/watch?v=PgeNPRBrB18&feature=youtu.be - erdekhayser
1
视频很棒,但是对于我来说没有修复警告。 :( - Mr Rogers
非常感谢这个视频 - 我从中学到了很多东西(相当令人惊讶)!然而,当我将视频的知识与https://dev59.com/nmMk5IYBdhLWcg3wyw_H?rq=1结合起来时,我可以修复警告。剩下的问题是:如何使scrollView的contentView大小仅为必要大小? - Wirsing
除了顶部和尾部间距之外,还要添加前导空间和底部空间。 - Ting Yi Shih
我刚刚在Size Inspector中将Ambiguity->Never Verify设置为了“不检查歧义”,这样就可以正常工作而不需要将UIView添加为子视图(实际上,这对我造成了一些问题)。我通常会在代码中完成其余工作。目前还没有发现任何问题。 - Paweł Pela
显示剩余5条评论
34个回答

2
如果您仍然遇到UIScrollView的问题,只需关闭“内容布局指南”(在xcode界面构建器中选择您的ScrollView -> 在右侧面板中选择大小检查器 -> 取消选择“内容布局指南”),或尝试以下步骤:xcode 11 scroll view layouts - 这对于新的布局样式可能很有用。在macOS 10.15.2,Xcode 11.3.1上运行良好,截至2020年5月2日。

请参见屏幕截图


2
import UIKit

class ViewController: UIViewController {

    //
    let scrollView: UIScrollView = {
        
        let sv = UIScrollView()
        sv.translatesAutoresizingMaskIntoConstraints = false
        return sv
    }()
    
    let lipsumLbl: UILabel = { // you can use here any of your subview
        
        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.text = """
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce eleifend nisi sit amet mi aliquet, ut efficitur risus pellentesque. Phasellus nulla justo, scelerisque ut libero id, aliquet ullamcorper lectus. Integer id iaculis nibh. Duis feugiat mi vitae magna tincidunt, vitae consequat dui tempor. Donec blandit urna nec urna volutpat, sit amet sagittis massa fringilla. Pellentesque luctus erat nec dui luctus, sed maximus urna fermentum. Nullam purus nibh, cursus vel ex nec, pulvinar lobortis leo. Proin ac libero rhoncus, bibendum lorem sed, congue sapien. Donec commodo, est non mollis malesuada, nisi massa tempus ipsum, a varius quam lorem vitae nunc.
        
        Cras scelerisque nisi dolor, in gravida ex ornare a. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas vitae nisl id erat sollicitudin accumsan sit amet viverra sapien. Etiam scelerisque vulputate ante. Donec magna nibh, pharetra sed pretium ac, feugiat sit amet mi. Vestibulum in ipsum vitae dui vehicula pulvinar eget ut lectus. Fusce sagittis a elit ac elementum. Fusce iaculis nunc odio, at fermentum dolor varius et. Suspendisse consectetur congue massa non gravida. Sed id elit vitae nulla aliquam euismod. Pellentesque accumsan risus dolor, eu cursus nibh semper id.
        
        Vestibulum vel tortor tellus. Suspendisse potenti. Pellentesque id sapien eu augue placerat dictum. Fusce tempus ligula at diam lacinia congue in ut metus. Maecenas volutpat tellus in tellus maximus imperdiet. Integer dignissim condimentum lorem, id luctus nisi maximus at. Nulla pretium, est sit amet mollis eleifend, tellus nulla viverra dolor, ac feugiat massa risus a lectus. Pellentesque ut pulvinar urna, blandit gravida ipsum. Nullam volutpat arcu nec fringilla auctor. Integer egestas enim commodo, faucibus neque ac, rutrum magna. Vivamus tincidunt rutrum auctor. Cras at rutrum felis. Fusce elementum lorem ut pharetra venenatis.
        """
        lbl.textColor = .darkGray
        lbl.font = UIFont.systemFont(ofSize: 16)
        lbl.numberOfLines = 0
        return lbl
    }()
    
    //======================================================================//
    //
    // MARK:- viewDidLoad
    //
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupViews()
        setupAutoLayout()
    }
    
    func setupViews() {
        
        title = "ScrollView Demo"
        view.backgroundColor = .white
        view.addSubview(scrollView)
        scrollView.addSubview(lipsumLbl)
    }
    
    func setupAutoLayout() {
        
        scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        
        lipsumLbl.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        lipsumLbl.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        lipsumLbl.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        lipsumLbl.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
    }
}

输出:

输入图像描述


最初的回答。

为什么你已经有了leftAnchor,还需要leadingAnchor呢? - ShadeToD
leftAnchor 不能用于此目的,因此我们必须使用 leadingAnchor 来实现相同的效果。 - iAj
你在例子中同时使用了两者。 - ShadeToD
@ShadeToD,让我检查一下。 - iAj

1

垂直滚动

  1. 在父视图中添加一个UIScrollView,并使用约束0,0,0,0。
  2. 在ScrollView中添加一个UIView,并使用约束0,0,0,0与父视图对齐。
  3. 为UIView添加相同的宽度约束到UIScrollView。
  4. 为UIView添加高度。
  5. 使用约束将元素添加到UIView中。
  6. 对于最靠近底部的元素,请确保它与UIView底部有约束。

1
对于任何视图,frame和其内容的大小是相同的,即如果您有一些内容(例如图像)的大小为200 * 800,则其框架也为200 * 800。
但是,这不适用于scrollView的内容。内容通常比scrollView的框架大小大。如果内容在宽度上相同,则仅垂直滚动。如果高度相同,则仅水平滚动。因此,这是唯一需要6个约束条件而不是4个约束条件的视图。对于具有超过4个必需约束条件的任何其他视图都会导致冲突。
为了设置滚动视图、其内容和滚动位置,您基本上需要回答三个问题:
1. 我的框架有多大?即在任何给定时刻,滚动视图应该有多大?例如,我只想使用屏幕高度的一半。
scrollview.frame = (x: 0, y:0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height / 2)
  1. 我需要多少滚动空间?即内容有多大?例如,框架可以只有500个点的宽度,但是您可以设置一个具有7000个点的图像,这将需要相当长的时间来水平滚动。或者让它恰好是500个点的宽度,这意味着不能进行水平滚动。

  2. 您目前已经滚动了多少?假设您的内容(或图像)宽度为7000,而帧大小仅为500。要到达图像的末尾,您需要向右滚动6500个点。第三部分实际上不影响约束条件。现在可以忽略它。理解它只是有助于了解scrollView的工作原理。

解决方案

基本上,如果省略两个额外的约束条件(关于内容大小),则布局引擎会因模糊性而发出警告。它不知道内容的哪个区域被隐藏(在scrollView中不可见),哪个区域没有(在scrollView中可见)隐藏。

因此,请确保还添加内容的大小约束。有关更多信息,请参见this answer

有时我并没有为我的视图添加尺寸限制,但它仍然可以正常工作。这是为什么呢?
如果你在滚动视图中添加的所有内容都被限制在滚动视图的边缘上,那么随着内容的增长,滚动视图会添加空间来适应。只是你可能正在使用其intrinsicContentSize为0的UIView,因此scrollView仍会抱怨其内容的歧义。但是,如果你使用了UILabel并且它具有非空文本,则其intrinsicContentSize将被设置(基于字体大小、文本长度和换行等),因此scrollView不会抱怨其歧义。

0

Xcode 11 +,Swift 5。

根据 @WantToKnow 的回答,我解决了我的问题,我准备好了 视频代码


这应该是一条注释。 - Nike Kov

0

我之前也遇到了同样的错误,后来我采取了以下步骤:

  1. 添加一个View(Superview)
  2. 添加ScrollView,大小为0,0,600,600
  3. 在ScrollView中添加UIView,大小为0,0,600,600
  4. 在UIView中添加图片视图和标签等

现在需要为scrollView(2)和UIView(3)加入leading/trailing/top/bottom。

选择View(1)和View(3),设置等高和等宽即可解决问题。

我录制了视频以帮助解决这个问题:

https://www.youtube.com/watch?v=s-CPN3xZS1A


0
你只需要确保有一种方法可以从滚动视图的顶部到底部形成连续的约束线。[-X-X-X]
在我的情况下 - 一个水平滚动的滚动视图 - 我还需要为滚动视图的每个子元素添加宽度和高度约束,尽管苹果的技术说明没有告诉你这一点。

0

0

如之前的回答所提到的,您应该在滚动视图中添加一个自定义视图:

The custom view is the ContentView marked in the image

将所有子视图添加到内容视图中。 在某个时候,您会看到滚动内容视图具有模糊的内容大小警告,您应该选择内容视图并单击IB布局右下角的“解决自动布局问题”按钮,并选择“添加缺少的约束”选项。

enter image description here

从现在开始,当您运行该项目时,滚动视图将自动更新其内容大小,无需额外的代码。


0
我所做的是创建一个单独的内容视图,如下所示。
内容视图是自由形式的,可以添加所有子视图,并根据其与contentView的约束进行设置。
UIScrollView被添加到主视图控制器中。
通过编程方式,我添加了通过IBOutlet链接到类的contentView,并将UIScrollView的contentView设置为它。

UIScrollView with ContentView via Interface Builder


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接