如何:在iOS中使用StackView视图从XIB自定义视图



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

我想要实现的

基本上就是与StackView具有相同的功能,当我将其直接拖动到故事板中时,它通常会具有相同的性能——在那里它会调整大小以适应没有问题的情况。这与自调整表视图单元格大小基本相同。

Showcase

我可以将CustomView简化为一个简单的例子:它是一个StackView,其中有三个标签,Alignment设置为CenterDistribution设置为Equal Centering。StackView将固定在左、右和顶部布局辅助线(或尾部、前导和顶部)。这我可以很容易地用CustomView重新创建,从XIB加载并转换为具有相同参数的CustomView——我将两者作为屏幕截图附上。

Simple StackView 屏幕截图

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会抱怨未知的高度。

我阅读了《自动布局指南》并观看了WWDC的视频"自动布局之谜",但这个话题对我来说仍然很新鲜,我认为我还没有找到正确的方法。所以这就是我需要帮助的地方

(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))
}

基本上,我确实拉伸了视图,以适应故事板中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中的表视图单元格那样设置约束?它总是会转换为类似label.top=top的东西,因此标签会被拉伸,而不是继承大小/高度的StackView。此外,我不能真正将StackView固定到XIB文件IB中的容器视图。

希望有人能帮助我。干杯

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

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

假设我禁用translatesAutoresizingMaskIntoConstraints,那么我不能像IB中的表视图单元格那样设置约束吗?它将永远翻译成类似label.top=top的东西,这样标签就会被拉伸,而不是继承大小/高度的StackView。而且我真的无法将StackView固定到IB中的容器视图XIB文件。

在本例中,您必须在代码中添加约束,因为您要从笔尖加载任意视图,然后将其添加到编码的CustomView中。笔尖中的视图不知道它将被放置在什么上下文或层次结构中,因此不可能提前定义如何将自己约束到未知的未来超视图中。如果故事板上或原型单元内的StackView,则该上下文及其父视图都是已知的。

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

(3)我是否以"建议的"方式加载XIB?

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

我不知道有"标准"或"正确"的方法来处理将笔尖加载到自定义视图中的问题。我看到了各种各样的方法,它们都有效。但是,我要注意的是,您在示例代码中所做的工作超出了需要。这里有一种实现相同结果的更简单的方法,但也使用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应该按预期布局。

最新更新