【问题标题】:How To: Self-Sizing Custom-View from XIB with StackView View in iOS如何:使用 iOS 中的 StackView 视图从 XIB 中自定义大小的自定义视图
【发布时间】:2017-04-28 20:24:02
【问题描述】:

我最近再次开始使用 Swift 进行 iOS 开发,现在正在研究自定义 UIControl。为了布局和灵活性,这个自定义视图是使用 StackView 构建的。

我想要达到的目标...

... 基本上是简单地与 StackView 具有相同的功能,当我将它直接拖到情节提要时通常具有的功能 - 它调整大小以适应没有问题。这与自调整大小的 tableview 单元格基本相同。

展示

我可以将我的 CustomView 简化为一个简单的示例:它是一个 StackView,其中包含三个标签,Alignment 设置为 CenterDistributionEqual Centering。 StackView 将固定到左、右和顶部布局指南(或尾随、前导和顶部)。我可以使用从 XIB 加载的 CustomView 轻松重新创建它,并将其转换为具有相同参数的 CustomView - 我将两者都附加为屏幕截图。

Screenshot of Simple StackView

Screenshot of CustomView

加载 XIB 文件并进行设置。

import UIKit

@IBDesignable
class CustomView: UIView {

    // loaded from NIB
    private weak var view: UIView!

    convenience init() {
        self.init(frame: CGRect())
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.loadNib()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.loadNib()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // we need to adjust the frame of the subview to no longer match the size used
        // in the XIB file BUT the actual frame we got assinged from the superview
        self.view.frame = bounds
    }

    private func loadNib ()  {
        self.view = Bundle (for: type(of: self)).loadNibNamed(
            "CustomView", owner: self, options: nil)! [0] as! UIView
        self.addSubview(self.view)
    }
}

您在这里可以看到,我只是加载了 XIB 文件,覆盖布局子视图以使框架适应实际视图,仅此而已。

问题

所以我的问题与通常如何处理有关。我的 StackView 中的标签具有内在的内容大小,即,通常 StackView 只是适应标签的高度而不会抱怨 - 如果您在情节提要中设计它。但是,如果您将所有这些都放在 XIB 中并保持布局原样,则不会 - 如果视图被固定在屏幕截图中的三种方式(顶部、前导、尾随),则 IB 会抱怨未知高度。

我阅读了 Auto-Layout Guide 并观看了 WWDC 视频“AutoLayout 的奥秘”,但这个主题对我来说仍然很新,我认为我还没有找到正确的方法。所以这就是我需要帮助的地方

(1) 我应该设置约束并禁用translatesAutoresizingMaskIntoConstraints 吗?

我首先可以并且应该做的是设置translatesAutoresizingMaskIntoConstraintsfalse,这样我就不会得到自动生成的约束并定义我自己的约束。所以我可以把loadNib改成这样:

private func loadNib ()  {
    self.view = Bundle (for: type(of: self)).loadNibNamed(
        "CustomView", owner: self, options: nil)! [0] as! UIView
    self.view.translatesAutoresizingMaskIntoConstraints = false
    self.addSubview(self.view)

    self.addConstraint(NSLayoutConstraint (
        item: self
        , attribute: .bottom
        , relatedBy: .equal
        , toItem: self.view
        , attribute: .bottom
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self
        , attribute: .top
        , relatedBy: .equal
        , toItem: self.view
        , attribute: .top
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self.view
        , attribute: .leading
        , relatedBy: .equal
        , toItem: self
        , attribute: .leading
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self.view
        , attribute: .trailing
        , relatedBy: .equal
        , toItem: self
        , attribute: .trailing
        , multiplier: 1.0
        , constant: 0))
}

基本上,我确实拉伸了视图以适应 Storyboard 中 UIView 的整个宽度,并将其限制在 StackView 的顶部和底部。

我在使用 XCode 8 时遇到了很多问题,所以我不太确定该怎么做。

(2) 还是应该覆盖intrinsicContentSize

我也可以通过检查所有arrangedSubviews 的高度来简单地覆盖intrinsicContentSize 并取最高的那个,将我的视图高度设置为它:

override var intrinsicContentSize: CGSize {
    var size = CGSize.zero

    for (_ , aSubView) in (self.view as! UIStackView).arrangedSubviews.enumerated() {
        let subViewHeight = aSubView.intrinsicContentSize.height
        if  subViewHeight > size.height {
            size.height = aSubView.intrinsicContentSize.height
        }
    }

    // add some padding
    size.height += 0
    size.width = UIViewNoIntrinsicMetric
    return size
}

虽然这肯定给了我更大的灵活性,但我还必须使内在内容大小无效,检查动态类型和许多我宁愿避免的其他东西。

(3) 我什至是以“建议”的方式加载 XIB 吗?

或者有没有更好的方法,例如通过使用UINib.instantiate?我真的找不到最佳实践方法来做到这一点。

(4) 为什么我必须首先这样做?

真的,为什么?我只是将 StackView 移到了 CustomView 中。那么为什么StackView现在就停止自适应了,为什么还要设置上下约束呢?

到目前为止,最有帮助的是https://stackoverflow.com/a/24441368 中的关键短语:

"一般来说,自动布局是以自上而下的方式执行的。在其他情况下 也就是说,先执行父视图布局,然后执行任何子视图 执行视图布局。”

.. 但我真的不太确定。

(5) 为什么我必须在代码中添加约束?

假设我禁用translatesAutoresizingMaskIntoConstraints,那么我不能为 IB 中的 tableview 单元格设置约束?它总是会转换为 label.top = top 之类的东西,因此标签会被拉伸,而不是 StackView 继承大小/高度。此外,对于 XIB 文件,我无法真正将 StackView 固定到 IB 中的容器视图。

希望有人可以帮助我。干杯!

【问题讨论】:

  • 没有人理解您的问题,这就是为什么它仍然处于打开状态。首先,您需要将您的堆栈视图完全固定到您的自定义视图中。然后它将根据自定义视图的框架调整大小。但是,如果您希望自定义视图根据堆栈视图调整大小,那么您需要将自定义视图固定到堆栈视图(反向/翻转约束)。自定义视图将展开以适应 stackview(stackview 将首先布局)。
  • 我当时正确地进行了固定,如果我没有从 XIB 文件加载它,它就可以工作。从那以后没有接触过这个项目,这就是我没有改变描述的原因。

标签: ios autolayout xib uistackview intrinsic-content-size


【解决方案1】:

以下是您问题的一些答案,尽管顺序不同:

(5) 为什么我必须在代码中添加约束?

假设我禁用 translatesAutoresizingMaskIntoConstraints 然后我 不能为 IB 中的 tableview 单元格设置约束?它会永远 翻译成类似 label.top = top 的东西,因此标签会 被拉伸而不是继承大小/高度的 StackView。还 我无法真正将 StackView 固定到 IB 中的容器视图 XIB 文件。

在本例中,您必须在代码中添加约束,因为您要从 nib 加载任意视图,然后将其添加到您的编码 CustomView。 nib 中的视图没有预先存在的关于它将被放置到什么上下文或层次结构中的知识,因此无法提前定义如何将自己约束到一个未知的未来超级视图。对于情节提要上或原型单元格内的 StackView,该上下文与其父视图一样被称为。

对于这种情况,如果您不想手动编写约束,您可以考虑的一个不错的替代方法是将您的 CustomView 子类化为 UIStackView 而不是 UIView。由于 UIStackView 会自动生成适当的约束,它将为您节省手动编码。请参阅下一个答案以获取一些示例代码。

(3) 我什至是以“建议”的方式加载 XIB 吗?

或者有没有更好的方法,例如通过使用 UINib.instantiate?我真的 找不到最佳实践方法。

我不知道将笔尖加载到自定义视图中的“标准”或“正确”方式。我见过各种可行的方法。但是,我会注意到您在示例代码中所做的工作比所需的要多。这是实现相同结果的更简单的方法,但也使用 UIStackView 子类而不是 UIView 子类(如上所述):

class CustomView: UIStackView {
    required init(coder: NSCoder) {
        super.init(coder: coder)
        distribution = .fill
        alignment = .fill
        if let nibView = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)?.first as? UIView {
            addArrangedSubview(nibView)
        }
    }
}

请注意,因为 CustomView 现在是 UIStackView 的子类,它会自动创建任何子视图所需的约束。在这种情况下,因为distributionalignment 都设置为.fill,它会根据需要将子视图的边缘固定到自己的边缘。

(4) 为什么我必须首先这样做?

真的,为什么?我只是将 StackView 移到了 CustomView 中。那为什么 StackView 现在停止自我调整,为什么我必须设置顶部 和底部约束?

实际上,这里的关键问题是您的 StackView 的 alignment 属性 .center。也就是说,StackView 中的标签将垂直居中,但不会将它们限制在 StackView 的顶部或底部。这就像在视图内的子视图中添加 centerY 约束一样。您可以将外部视图设置为您想要的任何高度,并且子视图将居中,但它不会“推出”外部视图的顶部和底部,因为中心约束仅约束中心,而不是顶部和底部边缘。

在您的故事板版本的 StackView 中,StackView 上方的视图层次结构是已知的,并且还有一些选项可以通过调整掩码大小来自动生成约束。无论哪种方式,UIKit 都知道最终的 StackView 高度,并且标签将在该高度内垂直居中,但实际上不会影响它。 您需要将 StackView 的对齐方式设置为 .fill,或者将标签顶部和底部的附加约束添加到 StackView 的顶部和底部,以便自动布局以确定 StackView 应该多高是。

(2) 或者我应该覆盖 intrinsicContentSize 吗?

这并没有多大帮助,因为内在内容大小本身不会推出父视图的边缘,除非子视图的边缘也被限制在父视图的边缘。无论如何,只要您解决上述.center 对齐问题,您将自动获得在这种情况下所需的所有正确的内在内容大小行为。

(1) 我应该设置约束并禁用 translatesAutoresizingMaskIntoConstraints 吗?

这通常是一种有效的方法。但是,如果您像我上面描述的那样将 UIStackView 子类化,那么您也不需要这样做。

总结

如果你:

  1. 将您的 CustomView 创建为 UIStackView 的子类,如上面的示例代码所示

  2. 在故事板上创建一个视图,设置为您的自定义类 CustomView。如果您希望 CustomView 正确地自行调整大小,则需要为其提供约束,而不是使用调整大小掩码中自动生成的约束。

  3. 将您笔尖中的 StackView 更改为将 alignment 设置为 .fill 或在至少一个标签的顶部和底部之间设置额外的约束(很可能是大的中心标签)和堆栈视图的顶部和底部

然后自动布局应该可以正常工作,并且您使用 StackView 的自定义视图应该按预期布局。

【讨论】:

  • 非常感谢您的回复 :) 回到 iOS 后我会尝试一下,非常感谢!
  • 如果在情节提要上添加自定义视图,则此方法有效,但如果您以编程方式将自定义视图添加到现有的 uistackview 中会怎样
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
  • 1970-01-01
  • 1970-01-01
  • 2012-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多