Ios 在不使用依赖于帧的intrinsicContentSize的情况下,视图如何基于其宽度的函数确定其高度?
我试图找出如何最好地创建一个视图,该视图根据给定的宽度确定其自身的高度。我想要的行为与垂直视图的行为非常相似,即:Ios 在不使用依赖于帧的intrinsicContentSize的情况下,视图如何基于其宽度的函数确定其高度?,ios,swift,uikit,Ios,Swift,Uikit,我试图找出如何最好地创建一个视图,该视图根据给定的宽度确定其自身的高度。我想要的行为与垂直视图的行为非常相似,即: 当受其顶部、前缘和后缘约束时,应根据其内容确定其自身的自然高度 当约束在所有边上时,它应通过根据这些约束确定的展开和折叠来填充所有可用空间 此外,我希望在视图内部不使用自动布局的情况下实现这一点 为了说明这一点,请考虑下面的例子: 类ViewController:UIViewController{ 设v=View() 重写func viewDidLoad(){ super.vi
- 当受其顶部、前缘和后缘约束时,应根据其内容确定其自身的自然高度
- 当约束在所有边上时,它应通过根据这些约束确定的展开和折叠来填充所有可用空间
- 此外,我希望在视图内部不使用自动布局的情况下实现这一点
类ViewController:UIViewController{
设v=View()
重写func viewDidLoad(){
super.viewDidLoad()
v、 translatesAutoresizingMaskIntoConstraints=false
视图。添加子视图(v)
NSLayoutConstraint.activate([
v、 topAnchor.constraint(等同于:视图.安全区域布局指南.topAnchor),
v、 leadingAnchor.constraint(等式:view.leadingAnchor),
v、 trailingAnchor.constraint(equalTo:view.trailingAnchor),
//打开和关闭此约束。
//v.bottomAnchor.constraint(相等于:视图.安全区域布局指南.bottomAnchor),
])
}
}
类视图:UIView{
//想象一下,这来自于计算一些子视图的大小
//它与边界的宽度有关。
var contentHeight:CGFloat{bounds.width*0.5}
var boundsWidth:CGFloat=0{
didSet{if oldValue!=boundsWidth{invalidateIntrinsicContentSize()}
}
重写变量intrinsicContentSize:CGSize{
.init(宽度:bounds.width,高度:contentHeight)
}
覆盖func布局子视图(){
super.layoutSubviews()
boundsWidth=bounds.width
背景颜色=.red
}
}
这个例子实现了我上面描述的行为,但是我对它有很大的保留,因为我使用的是intrinsicContentSize
声明它必须独立于我尚未管理的内容框架。我正在根据帧的宽度计算内部大小
如何才能实现此行为并且不使
instrinsicContentSize
依赖于边界 这一切都可以通过自动布局约束来完成。不需要计算任何东西
您需要做的就是确保自定义视图的内容具有适当的约束来定义布局
例如,UILabel
具有基于其文本的固有大小。可以将“顶部”标签约束到视图的顶部,“中间”标签约束到“顶部”标签的底部,“底部”标签约束到“中间”标签的底部和约束到视图的底部
下面是一个示例(全部通过代码):
结果是:
并旋转,因此您可以看到自动调整大小:
编辑 关于固有内容大小的一点澄清 在此图像中,所有5个子视图的
intrinsicContentSize
值均为120 x 80
:
class IntrinsicTestView: UIView {
override var intrinsicContentSize: CGSize {
return CGSize(width: 120, height: 80)
}
}
如你所见:
- 如果我不添加约束来指定宽度,则视图的宽度将为120磅
- 如果我不添加约束来指定高度,则视图的高度将为80分
- 否则,宽度和高度将由我添加的约束决定
class IntrinsicTestView: UIView {
override var intrinsicContentSize: CGSize {
return CGSize(width: 120, height: 80)
}
}
class IntrinsicExampleViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var iViews: [IntrinsicTestView] = [IntrinsicTestView]()
var v: IntrinsicTestView
let colors: [UIColor] = [.red, .green, .blue, .yellow, .purple]
colors.forEach { c in
let v = IntrinsicTestView()
v.backgroundColor = c
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
iViews.append(v)
}
let g = view.safeAreaLayoutGuide
// first view at Top: 20 / Leading: 20
v = iViews[0]
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0).isActive = true
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0).isActive = true
// second view at Top: 120 / Leading: 20
// height: 20
v = iViews[1]
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 120.0).isActive = true
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0).isActive = true
v.heightAnchor.constraint(equalToConstant: 20).isActive = true
// third view at Top: 160 / Leading: 20
// height: 40 / width: 250
v = iViews[2]
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 160.0).isActive = true
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0).isActive = true
v.heightAnchor.constraint(equalToConstant: 40).isActive = true
v.widthAnchor.constraint(equalToConstant: 250).isActive = true
// fourth view at Top: 220
// trailing: 20 / width: 250
v = iViews[3]
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 220.0).isActive = true
v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0).isActive = true
v.widthAnchor.constraint(equalToConstant: 250).isActive = true
// fourth view at Top: 400 / Leading: 20
// trailing: 20 / bottom: 20
v = iViews[4]
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 400.0).isActive = true
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0).isActive = true
v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0).isActive = true
v.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0).isActive = true
}
}
正如DonMag在他的回答中所说,
intrinsicContentSize
并不完全用于复杂的布局管理,而是在没有足够的框架约束时作为视图首选大小的指示
不过,我可以提供一个简单的例子,希望能为您指明正确的方向。让我们假设我想要一个
比率视图
具有固有的大小,以便其高度是其宽度的线性函数:
let ratioView = RatioView()
ratioView.ratio = 0.5
在本例中,ratioView
的固有高度等于其宽度的一半
比率视图的代码如下所示:
class RatioView: UIView {
var contentHeight: CGFloat { bounds.width * ratio }
private var kvoObservation: NSKeyValueObservation?
var ratio: CGFloat = 1 {
didSet { invalidateIntrinsicContentSize() }
}
override var intrinsicContentSize: CGSize {
.init(width: bounds.width, height: contentHeight)
}
override func didMoveToSuperview() {
guard let _ = superview else { return }
kvoObservation = observe(\.frame, options: .initial) { [weak self] (_, change) in
self?.invalidateIntrinsicContentSize()
}
}
override func willMove(toSuperview newSuperview: UIView?) {
if newSuperview == nil {
kvoObservation = nil
}
}
}
上述代码中有两个重要亮点:
- 视图调用以通知布局系统其
属性的值已更改intrinsicContentSize
- 视图通过对其
属性的任何更改不断进行监视,以便在每次宽度更改时计算新高度frame
第二步特别值得注意:如果没有它,视图将不知道其大小何时发生变化,因此没有任何机会通知布局系统(通过
invalidateIntrinsicContentSize
)其intrinsicContentSize
已刷新。您提供了更多详细信息,但你到底想做什么还不清楚。“创建一个根据给定宽度确定自身高度的视图”-是否希望高度为宽度的百分比?您是否计划将子视图添加到此视图,并且希望子视图的高度确定视图的高度?谢谢您的答复!我跳过了这些要点,可能是错误的,因为我认为它们有损于我的实际问题!我正在处理的特定视图将是许多较小子视图的容器,我将在内部定位这些子视图。为了简化事情,你可以画一个垂直视图。我在示例代码中使用了宽度的一个百分比,希望人们能够理解
class RatioView: UIView {
var contentHeight: CGFloat { bounds.width * ratio }
private var kvoObservation: NSKeyValueObservation?
var ratio: CGFloat = 1 {
didSet { invalidateIntrinsicContentSize() }
}
override var intrinsicContentSize: CGSize {
.init(width: bounds.width, height: contentHeight)
}
override func didMoveToSuperview() {
guard let _ = superview else { return }
kvoObservation = observe(\.frame, options: .initial) { [weak self] (_, change) in
self?.invalidateIntrinsicContentSize()
}
}
override func willMove(toSuperview newSuperview: UIView?) {
if newSuperview == nil {
kvoObservation = nil
}
}
}