【问题标题】:UIScrollView that expands with contents' instrinsic size until height X, and then scrollsUIScrollView 会随着内容的固有大小展开直到高度 X,然后滚动
【发布时间】:2016-10-20 05:06:00
【问题描述】:

我基本上是在尝试重现警报标题和消息部分的行为。

标题和消息标签似乎在滚动视图中。如果标签文本增加,则警报高度也会随着标签的内在内容大小而增加。但在一定高度,警报高度停止增加,标题和消息文本变为可滚动。

我读过的内容:

文章

堆栈溢出

答案可能在那里,但我无法抽象出来。

我尝试过的:

只关注带有两个标签的滚动视图我试图做一个最小的例子,其中父视图将根据滚动视图的固有高度调整大小。我玩过很多限制。这是一个不起作用的组合(在许多组合中):

我曾与auto layoutnormal constraints 甚至intrinsic content sizes 合作过。另外,我知道how to get a basic scroll view working with auto layout。然而,我从来没有做过任何优先级和内容拥抱和压缩阻力的事情。从我所做的阅读中,我对它们的含义有一个肤浅的理解,但我不知道如何在这种情况下应用它们。我的猜测是我需要做一些内容拥抱和优先事项。

【问题讨论】:

  • 滚动视图没有固有大小 - 你有子类化它吗?
  • @Wain。我没有对滚动视图进行子类化,但由于UILabel 确实具有固有的内容大小,我试图让滚动视图的内容视图根据它包含的标签的大小将大小增加到一个点.
  • 如果你固定边缘应该,但这只是意味着你没有很好地解释你的问题......
  • @Wain,抱歉,我最后的评论不正确。内容视图大小正在增加。我正在尝试获取滚动视图的父级(以及滚动视图本身)以随着内容视图大小的增加而增加大小。当父级达到某个最大大小时,滚动视图的内容视图大小的任何增加都会导致正常的滚动行为。我开始认为这只能通过在运行时手动更改约束来实现。

标签: ios uiscrollview autolayout intrinsic-content-size


【解决方案1】:

你可以通过两个约束相当简单地做到这一点

  • 约束 1:ScrollView.height
  • 约束 2:ScrollView.height = ScrollView.contentSize.height。优先级 = DefaultHigh

AutoLayout 会“尝试”将 scrollView 保持在 contentSize,但当它匹配最大高度时会“放弃”并停在那里。

唯一棘手的部分是设置约束 2 的高度。

当我的 UIStackView 在 UIViewController 中时,我会在 viewWillLayoutSubviews 中这样做

如果您将 UIScrollView 子类化以实现此目的,您可以在 updateConstraints 中进行操作

类似

override func viewWillLayoutSubviews() {

    scrollViewHeightConstraint?.constant = scrollView.contentSize.height     

    super.viewWillLayoutSubviews()
}

【讨论】:

    【解决方案2】:

    我已经能够仅使用 AutoLayout 约束来实现这种精确的行为。下面是一个通用演示:它可以应用到您认为合适的视图层次结构中。

    import UIKit
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let kTestContentHeight: CGFloat = 1200
    
            // Subview that will shrink to fit content and expand up to 50% of the view controller's height
            let modalView = UIView()
            modalView.backgroundColor = UIColor.gray
    
            // Scroll view that will facilitate scrolling if the content > 50% of view controller's height
            let scrollView = UIScrollView()
            scrollView.backgroundColor = .yellow
    
            // Content which has an intrinsic height
            let contentView = UIView()
            contentView.backgroundColor = .green
    
    
            // add modal view
            view.addSubview(modalView)
            modalView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([modalView.leftAnchor.constraint(equalTo: view.leftAnchor),
                                         modalView.rightAnchor.constraint(equalTo: view.rightAnchor),
                                         modalView.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor,
                                                                           multiplier: 0.5),
                                         modalView.bottomAnchor.constraint(equalTo: view.bottomAnchor)])
    
            let expandHeight = modalView.heightAnchor.constraint(equalTo: view.heightAnchor)
            expandHeight.priority = UILayoutPriority.defaultLow
            expandHeight.isActive = true
    
            // add scrollview to modal view
            modalView.addSubview(scrollView)
            scrollView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([scrollView.topAnchor.constraint(equalTo: modalView.topAnchor),
                                         scrollView.leftAnchor.constraint(equalTo: modalView.leftAnchor),
                                         scrollView.rightAnchor.constraint(equalTo: modalView.rightAnchor),
                                         scrollView.bottomAnchor.constraint(equalTo: modalView.bottomAnchor)])
    
    
            // add content to scrollview
            scrollView.addSubview(contentView)
            contentView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([contentView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
                                         contentView.widthAnchor.constraint(equalTo: modalView.widthAnchor),
                                         contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
                                         contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
                                         contentView.heightAnchor.constraint(equalToConstant: kTestContentHeight)])
            let contentBottom = contentView.bottomAnchor.constraint(equalTo: modalView.bottomAnchor)
            contentBottom.priority = .defaultLow
            contentBottom.isActive = true
        }
    
    }
    

    【讨论】:

      【解决方案3】:

      对于我的项目,我有类似的问题。您可以使用以下方法使其工作。

      首先,标题和底部动作高度是固定的。内容具有可变高度。可以使用font-size将它作为一个child添加到mainView中,然后调用layoutIfNeeded,就可以计算出它的高度并保存为XX。然后将其从 mainView 中删除。

      其次,使用普通约束来布局内容部分与scrollView,mainView的高度约束为XX和setContentCompressionResistancePriority(.defaultLow, for: .vertical)。

      最后,alert 可以在短内容时显示精确尺寸,而在长尺寸滚动时显示有限尺寸。

      【讨论】:

        【解决方案4】:

        我想我已经实现了类似于你想要的纯自动布局的效果。

        结构

        首先,让我展示一下我的结构:

        内容视图是具有白色背景的视图,调用者视图和底部视图具有固定的高度。底视图有你的按钮,来电者视图有你的标题。

        解决方案

        因此,在设置了基本约束之后(请注意,滚动视图内的视图具有滚动视图的顶部、左侧、右侧和底部以及相等的宽度),问题是滚动视图不知道应该具有什么大小. 所以这就是我所做的:

        我希望卷轴可以增长到最大。所以我向设置最大值的超级视图添加了一个比例高度:

        但是,这带来了两个问题:Scroll View 仍然不知道应该有什么高度,现在您可以调整大小并且滚动视图将传递他的内容的大小(如果内容小于最大大小)。

        所以,为了解决这两个问题,我从滚动视图和滚动视图内部的视图中添加了一个相等的高度具有较小的优先级

        希望对你有帮助。

        【讨论】:

        • 这看起来很有趣。你真的有什么工作吗?我很乐意将此标记为新解决方案,但我目前正在从事其他项目,因此我没有时间进行大量测试。不过,这是我想回到的事情。
        • @Suragch 尽可能测试它,我有时间:p。我一测试基础知识就发布了,但我会继续使用这个解决方案并增加它的复杂性。我可以继续反馈:)
        • 我很难理解这是如何工作的,但既然你已经确认基础工作,我会将其标记为新接受的答案。欢迎您想在答案中编辑的任何其他详细信息或未来改进。
        • 在我的测试中,这仅在 UIScrollView(标签等)的内容不大于 UIScrollView 的最大高度时才有效。只需添加一个非常长的文本,使 View 增长超过使用第一个约束定义的最大高度 - 65% 的超级视图。但是,在这种情况下,文本将被截断...
        • 为了使其滚动,而不是将约束(与滚动视图和滚动视图内部的视图相同的高度,具有较小优先级)设置为优先级 999,将其设置为 249。这是因为优先级 249 低于固有内容大小的优先级。在这种情况下,滚动视图的高度将根据其内部的视图而增长,但上限为最大值。由于优先级为 249,即使达到 Scroll View 的最大值,Scroll View 内部的 View 也可以继续增长,从而增加 Scroll View 的 contentSize 使其可滚动
        【解决方案5】:

        可以在界面生成器中使用自动布局轻松完成。

        1. 使用“小于或等于”关系设置外部容器视图(示例中为“滚动视图的父容器”)高度约束。

        2.向内容视图和滚动视图添加“等高”约束。然后将此约束设置为低优先级。

        就是这样。现在,如果内容高度发生变化,您的滚动视图将按高度调整大小,但仅限于受外部视图高度约束限制的最大高度。

        【讨论】:

        • 我今天浪费了很多时间。在我找到你的答案之前,几个小时没有任何结果。谢谢!表扬你。
        【解决方案6】:

        您应该能够通过纯自动布局来实现此解决方案。

        通常,如果我希望标签随着内容垂直增长而增长,我会这样做

        [label setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; 
        [label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
        

        为了让您的滚动视图符合您的要求,您需要确保可以绘制一条线,将滚动视图的顶部一直通过标签连接到滚动视图的底部,以便计算它的高度。为了让滚动视图限制在它的父级上,你可以设置一个高度约束,比如 0.8 的父视图的乘数

        【讨论】:

        • I've been able to get labels to grow vertically with their content 不改变内容拥抱和压缩阻力。为什么要设置它们?
        • 该解决方案使用preferredMaxLayoutWidth。当我没有设置它并使用左/右约束来超级视图或将其嵌套在具有可变高度的视图中时,我的标签不会在没有它的情况下换行。如果您的标签已经在增长。将滚动视图高度设置为小于或等于超级视图乘数应该可以解决您的问题。
        • 我正在努力让您的答案生效。您能否提供有关内容拥抱和抗压缩性的任何示例详细信息?
        【解决方案7】:

        您的问题不能仅通过约束来解决,您必须使用一些代码。这是因为滚动视图没有固有的内容大小。

        所以,创建一个滚动视图的子类。也许给它一个属性或一个委托或其他东西来告诉它它的最大高度应该是多少。

        在子类中,每当内容大小发生变化时,使内在内容大小无效,并将新的内在大小计算为内容大小和允许的最大大小中的最小值。

        一旦滚动视图具有内在大小,您在它和它的超级视图之间的约束实际上会对您的问题做一些有意义的事情。

        【讨论】:

        • 好的。这太糟糕了,它需要子类化,但至少我不会因为无法获得纯自动布局解决方案而感到难过。所以你认为标准警报控制器使用UIScrollView 子类?
        • 这是一个猜测,还有其他方法可以做到,但关键是如果没有应用更多信息/逻辑,自动布局无法做到。
        • 我正在更改接受的答案,因为事实证明只有自动布局的答案是可能的。
        猜你喜欢
        • 2019-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-27
        • 2023-03-12
        相关资源
        最近更新 更多