Apple-intrinsicContentSizeswift
The natural size for the receiving view, considering only properties of the view itself.markdown
A size indicating the natural size for the receiving view based on its intrinsic properties.app
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.ide
也就是說,若是系統佈局沒法根據當前約束來肯定視圖大小,自定義view能夠重寫屬性intrinsicContentSize來決定本身的大小。oop
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)
測試
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)")
}
複製代碼
控制檯輸出:ui
label intrinsic content size: (41.333333333333336, 20.333333333333332)
複製代碼
說明對於有text內容的控件,UILabel,intrinsicContentSize的值會自動被設置。this
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)")
}
複製代碼
控制檯輸出:spa
button intrinsic content size: (54.0, 34.0)
複製代碼
說明UIButton也是和UILabel同樣,intrinsicContentSize的值會被自動設置。
直接新建個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。
看來和輸出controller的view的結果同樣。
final class DemoView: UIView {
override var intrinsicContentSize: CGSize {
return CGSize(width: 100, height: 50)
}
}
複製代碼
再運行,能夠發現輸出:
demoView intrinsic content size: (100.0, 50.0)
複製代碼
並且界面上出現了demoView
這就說明了若是系統佈局沒法根據當前約束來肯定視圖大小,自定義view能夠重寫屬性intrinsicContentSize來決定本身的大小。
final class DemoView: UIView {
override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: 50)
}
}
複製代碼
運行,輸出:
demoView intrinsic content size: (-1.0, 50.0)
複製代碼
界面上沒出現demoView:
我的猜想:系統認爲你的width設置成UIView.noIntrinsicMetric
,就是表明你在約束上給了可肯定的結果。實際上並無可肯定的寬度的約束,因此就顯示失敗了。是否是這樣呢?
NSLayoutConstraint.activate([
...
demoView.widthAnchor.constraint(equalTo: label.widthAnchor)
])
複製代碼
運行,輸出:
demoView intrinsic content size: (-1.0, 50.0)
複製代碼
界面上出現了demoView,並且寬度和label的一致:
這就說明若是寬度約束能讓系統肯定寬度的狀況下,能夠給intrinsicContentSize的width設置爲UIView.noIntrinsicMetric
,同理,height也同樣。
項目中須要實現dynamic scaling動態縮放功能,相應控件要隨着系統的縮放級別而變化。
先來看看拆分視圖上的控件狀況:
先理清楚一些細節:
①UITableView並無設置自動估算行高,也沒有使用heightForRow方法返回固定的行高。
在②UITableViewCell裏面的init方法裏給自定義View③作了自動佈局,距離上下左右,但沒有設置高度。
③自定義View裏面的④UICollectinView佈局也是距離上下左右,沒有設置高度。
③自定義View裏面重寫了intrinsicContentSize屬性,根據縮放比例算出cell的高度,寬度使用的是UIView.noIntrinsicMetric。
設置縮放級別,會觸發tableView的cellForRow方法,setupCell,③的size會根據intrinsicContentSize而獲得,因此界面會實時縮放。
④UICollectinView的flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize,並沒什麼用。
在這個基礎上,把重寫了intrinsicContentSize屬性的邏輯註釋掉,會發生什麼?
啥都沒了。只剩下個空空如也的Cell。
這時候去設置tableView的自動估算高度:
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
複製代碼
再運行:
、
仍是空空如也,說明自定義UIView塞在cell裏,tableView是沒辦法自動估計到UIView的高度的。因此最簡單的方式就是設置③自定義View裏的intrinsicContentSize
。
若是不使用intrinsicContentSize
,那就須要在③自定義View裏開放個屬性來計算數據實際高度,而後在tableView的heightForCell裏寫死這個高度了。
intrinsicContentSize
的UIView裏進行監聽,這樣就有完整的封裝性,而不是在UIView外部): NotificationCenter.default.addObserver(self, selector: #selector(didChangeContentSizeCategory), name: UIContentSizeCategory.didChangeNotification, object: nil)
複製代碼
invalidateIntrinsicContentSize()
,系統會從新給UIView設置intrinsicContentSize
,這樣界面就會實現實時縮放: @objc private func didChangeContentSizeCategory() {
invalidateIntrinsicContentSize()
...
}
複製代碼