iOS intrinsicContentSize的探索

一、intrinsicContentSize是什么?先看苹果官方的介绍:

Apple-intrinsicContentSize

The natural size for the receiving view, considering only properties of the view itself.

Return Value:

A size indicating the natural size for the receiving view based on its intrinsic properties.

Discussion:

The default width and height values of this property are set to NSViewNoInstrinsicMetric. For a custom view, you can override this property and use it to communicate what size you would like your view to be based on its content. You might do this in cases where the layout system cannot determine the size of the view based solely on its current constraints. For example, a text field might override this method and return an intrinsic size based on the text it contains. The intrinsic size you supply must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height. If your custom view has no intrinsic size for a given dimension, you can set the corresponding dimension to the NSViewNoInstrinsicMetric.

也就是说,如果系统布局无法根据当前约束来确定视图大小,自定义view可以重写属性intrinsicContentSize来决定自己的大小。

二、写个demo测试一下:

建个空的viewController,在viewDidLoad()打印view的intrinsicContentSize:
override func viewDidLoad() {
    super.viewDidLoad()
    print("view intrinsic content size: \(view.intrinsicContentSize)")
}
复制代码

控制台输出:

view intrinsic content size: (-1.0, -1.0)
复制代码

说明对于一个view来说,默认的intrinsicContentSize值是(-1.0, -1.0)

换个label试试,设置布局居中:
private var label: UILabel = {
    let label = UILabel()
    label.backgroundColor = .red
    label.text = "Label"
    label.textColor = .black
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}()
    
override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(label)
    NSLayoutConstraint.activate([
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
    print("label intrinsic content size: \(label.intrinsicContentSize)")
}
复制代码

控制台输出:

label intrinsic content size: (41.333333333333336, 20.333333333333332)
复制代码

image.png

说明对于有text内容的控件,UILabel,intrinsicContentSize的值会自动被设置。

那给UIButton设个text,是不是也能像label那样呢?
private var button: UIButton = {
    let button = UIButton()
    button.backgroundColor = .yellow
    button.setTitle("Button", for: .normal)
    button.setTitleColor(.black, for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false
    return button
}()

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    view.addSubview(button)
    NSLayoutConstraint.activate([
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        button.topAnchor.constraint(equalTo: label.bottomAnchor)
    ])
    print("button intrinsic content size: \(button.intrinsicContentSize)")
}
复制代码

控制台输出:

button intrinsic content size: (54.0, 34.0)
复制代码

image.png

说明UIButton也是和UILabel一样,intrinsicContentSize的值会被自动设置。

现在试试自定义的UIView

直接新建个DemoView类

final class DemoView: UIView {}
复制代码

把它也加到controller的view上去

private var demoView: DemoView = {
    let view = DemoView()
    view.backgroundColor = .blue
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    view.addSubview(demoView)
    NSLayoutConstraint.activate([
        demoView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        demoView.topAnchor.constraint(equalTo: button.bottomAnchor)
    ])
    print("demoView intrinsic content size: \(demoView.intrinsicContentSize)")
}
复制代码

控制台输出:

demoView intrinsic content size: (-1.0, -1.0)
复制代码

而且模拟器上并未出现demoView。

image.png

看来和输出controller的view的结果一样。

在DemoView里重写intrinsicContentSize试试
final class DemoView: UIView {
    override var intrinsicContentSize: CGSize {
        return CGSize(width: 100, height: 50)
    }
}
复制代码

再运行,可以发现输出:

demoView intrinsic content size: (100.0, 50.0)
复制代码

而且界面上出现了demoView

image.png

这就说明了如果系统布局无法根据当前约束来确定视图大小,自定义view可以重写属性intrinsicContentSize来决定自己的大小。

再试试如果改变width的值为UIView.noIntrinsicMetric:
final class DemoView: UIView {
    override var intrinsicContentSize: CGSize {
        return CGSize(width: UIView.noIntrinsicMetric, height: 50)
    }
}
复制代码

运行,输出:

demoView intrinsic content size: (-1.0, 50.0)
复制代码

界面上没出现demoView:

image.png

个人猜测:系统认为你的width设置成UIView.noIntrinsicMetric,就是代表你在约束上给了可确定的结果。实际上并没有可确定的宽度的约束,所以就显示失败了。是不是这样呢?

来改变一下demoView的宽度约束和label的一致:
NSLayoutConstraint.activate([
    ...
    demoView.widthAnchor.constraint(equalTo: label.widthAnchor)
])
复制代码

运行,输出:

demoView intrinsic content size: (-1.0, 50.0)
复制代码

界面上出现了demoView,而且宽度和label的一致:

image.png

这就说明如果宽度约束能让系统确定宽度的情况下,可以给intrinsicContentSize的width设置为UIView.noIntrinsicMetric,同理,height也一样。

三、实际项目中的应用情况:

项目中需要实现dynamic scaling动态缩放功能,相应控件要随着系统的缩放级别而变化。

demo.gif

先来看看拆分视图上的控件情况:

image.png

先理清楚一些细节:

  1. ①UITableView并没有设置自动估算行高,也没有使用heightForRow方法返回固定的行高。

  2. ②UITableViewCell里面的init方法里给自定义View③做了自动布局,距离上下左右,但没有设置高度。

  3. ③自定义View里面的④UICollectinView布局也是距离上下左右,没有设置高度。

  4. ③自定义View里面重写了intrinsicContentSize属性,根据缩放比例算出cell的高度,宽度使用的是UIView.noIntrinsicMetric。

  5. 设置缩放级别,会触发tableView的cellForRow方法,setupCell,③的size会根据intrinsicContentSize而得到,所以界面会实时缩放。

  6. ④UICollectinView的flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize,并没什么用。

在这个基础上,把重写了intrinsicContentSize属性的逻辑注释掉,会发生什么?

image.png

啥都没了。只剩下个空空如也的Cell。

这时候去设置tableView的自动估算高度:

tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
复制代码

再运行:

image.png

还是空空如也,说明自定义UIView塞在cell里,tableView是没办法自动估计到UIView的高度的。所以最简单的方式就是设置③自定义View里的intrinsicContentSize

如果不使用intrinsicContentSize,那就需要在③自定义View里开放个属性来计算数据实际高度,然后在tableView的heightForCell里写死这个高度了。

四、参考文献

developer.apple.com/documentati…

medium.com/@vialyx/imp…

www.hackingwithswift.com/example-cod…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享